@@ -18,139 +18,182 @@ limitations under the License.
1818package e2e
1919
2020import (
21+ "strings"
2122 "testing"
2223 "time"
2324
2425 "github.com/apache/incubator-devlake/core/models/common"
26+ "github.com/apache/incubator-devlake/core/models/domainlayer"
2527 "github.com/apache/incubator-devlake/core/models/domainlayer/codequality"
28+ coremigrations "github.com/apache/incubator-devlake/core/models/migrationscripts"
29+ "github.com/apache/incubator-devlake/core/plugin"
2630 "github.com/apache/incubator-devlake/helpers/e2ehelper"
31+ implcontext "github.com/apache/incubator-devlake/impls/context"
2732 "github.com/apache/incubator-devlake/plugins/sonarqube/impl"
2833 "github.com/apache/incubator-devlake/plugins/sonarqube/models"
34+ sonarqubemigrations "github.com/apache/incubator-devlake/plugins/sonarqube/models/migrationscripts"
2935 "github.com/apache/incubator-devlake/plugins/sonarqube/tasks"
30- "github.com/stretchr/testify/assert "
36+ "github.com/stretchr/testify/require "
3137)
3238
33- // TestSonarqubeIssueCodeBlockLongComponent verifies that the convertIssueCodeBlocks
34- // subtask can handle component paths longer than 256 and 500 characters without
35- // producing "Data too long for column 'component'" errors.
36- //
37- // This test reproduces the original bug:
38- //
39- // Error 1406 (22001): Data too long for column 'component' at row 91
40- //
41- // The fix changed both tool layer and domain layer `component` columns to TEXT type.
39+ type sonarqubeIssueCodeBlockBeforeText struct {
40+ ConnectionId uint64 `gorm:"primaryKey"`
41+ Id string `gorm:"primaryKey"`
42+ IssueKey string `gorm:"index"`
43+ Component string `gorm:"index;type:varchar(500)"`
44+ StartLine int
45+ EndLine int
46+ StartOffset int
47+ EndOffset int
48+ Msg string
49+ common.NoPKModel
50+ }
51+
52+ func (sonarqubeIssueCodeBlockBeforeText ) TableName () string {
53+ return "_tool_sonarqube_issue_code_blocks"
54+ }
55+
56+ type cqIssueCodeBlockBeforeText struct {
57+ domainlayer.DomainEntity
58+ IssueKey string `json:"key" gorm:"index"`
59+ Component string `gorm:"index"`
60+ StartLine int
61+ EndLine int
62+ StartOffset int
63+ EndOffset int
64+ Msg string
65+ }
66+
67+ func (cqIssueCodeBlockBeforeText ) TableName () string {
68+ return "cq_issue_code_blocks"
69+ }
70+
4271func TestSonarqubeIssueCodeBlockLongComponent (t * testing.T ) {
4372 var sonarqube impl.Sonarqube
4473 dataflowTester := e2ehelper .NewDataFlowTester (t , "sonarqube" , sonarqube )
45-
46- // Migrate the tables to get the current schema (with TEXT type for component)
4774 dataflowTester .FlushTabler (& models.SonarqubeIssue {})
48- dataflowTester .FlushTabler (& models.SonarqubeIssueCodeBlock {})
49- dataflowTester .FlushTabler (& codequality.CqIssueCodeBlock {})
50-
51- // Create a component path that exceeds 256 chars (old GORM default varchar limit)
52- longComponent256 := "my-org_my-project-key:src/main/java/com/myorganization/myapplication/services/implementations/deeply/nested/package/structure/subpackage/another/level/of/nesting/MyVeryLongServiceImplementationClassNameThatExceedsThe256CharacterLimit.java"
53- assert .Greater (t , len (longComponent256 ), 256 )
54-
55- // Create a component path that exceeds 500 chars (previous varchar(500) limit)
56- longComponent500 := "my-organization_my-extremely-long-project-name-with-feature-branch:" +
57- "src/main/java/com/enterprise/application/modules/customer/services/implementations/" +
58- "internal/validation/rules/complex/nested/deeply/structured/package/hierarchy/" +
59- "subsystem/core/domain/entities/aggregates/valueobjects/specifications/" +
60- "MyExtremelyLongAndDescriptiveClassNameForAnEnterpriseApplicationServiceImplementation" +
61- "ThatFollowsAllNamingConventionsAndExceedsReasonableLimits.java"
62- assert .Greater (t , len (longComponent500 ), 500 )
63-
64- // Insert test issue (needed for the JOIN in the converter)
75+ require .NoError (t , dataflowTester .Db .Migrator ().DropTable (
76+ & sonarqubeIssueCodeBlockBeforeText {},
77+ & cqIssueCodeBlockBeforeText {},
78+ ))
79+ require .NoError (t , dataflowTester .Db .AutoMigrate (
80+ & sonarqubeIssueCodeBlockBeforeText {},
81+ & cqIssueCodeBlockBeforeText {},
82+ ))
83+
84+ existingComponent := "existing:component"
85+ require .NoError (t , dataflowTester .Db .Create (& sonarqubeIssueCodeBlockBeforeText {
86+ ConnectionId : 1 ,
87+ Id : "existing-tool-block" ,
88+ IssueKey : "existing-issue" ,
89+ Component : existingComponent ,
90+ }).Error )
91+ require .NoError (t , dataflowTester .Db .Create (& cqIssueCodeBlockBeforeText {
92+ DomainEntity : domainlayer.DomainEntity {Id : "existing-domain-block" },
93+ IssueKey : "existing-domain-issue" ,
94+ Component : existingComponent ,
95+ }).Error )
96+
97+ basicRes := implcontext .NewDefaultBasicRes (dataflowTester .Cfg , dataflowTester .Log , dataflowTester .Dal )
98+ runMigration (t , coremigrations .All (), "change cq_issue_code_blocks.component type to text" , basicRes )
99+ runMigration (t , sonarqubemigrations .All (), "change _tool_sonarqube_issue_code_blocks.component type to text" , basicRes )
100+ assertTextColumnWithoutIndex (t , dataflowTester , "cq_issue_code_blocks" )
101+ assertTextColumnWithoutIndex (t , dataflowTester , "_tool_sonarqube_issue_code_blocks" )
102+
103+ var migratedToolBlock sonarqubeIssueCodeBlockBeforeText
104+ require .NoError (t , dataflowTester .Db .First (& migratedToolBlock , "id = ?" , "existing-tool-block" ).Error )
105+ require .Equal (t , existingComponent , migratedToolBlock .Component )
106+ var migratedDomainBlock cqIssueCodeBlockBeforeText
107+ require .NoError (t , dataflowTester .Db .First (& migratedDomainBlock , "id = ?" , "existing-domain-block" ).Error )
108+ require .Equal (t , existingComponent , migratedDomainBlock .Component )
109+ require .NoError (t , dataflowTester .Db .Delete (& migratedToolBlock ).Error )
110+ require .NoError (t , dataflowTester .Db .Delete (& migratedDomainBlock ).Error )
111+
112+ longComponent256 := "project:" + strings .Repeat ("a" , 256 )
113+ longComponent500 := "project:" + strings .Repeat ("b" , 500 )
114+ require .Greater (t , len (longComponent256 ), 256 )
115+ require .Greater (t , len (longComponent500 ), 500 )
116+
65117 issueKey := "TEST-LONG-COMPONENT-ISSUE"
66118 projectKey := "test-long-component-project"
67- testIssue := & models.SonarqubeIssue {
119+ result := dataflowTester . Db . Create ( & models.SonarqubeIssue {
68120 ConnectionId : 1 ,
69121 IssueKey : issueKey ,
70122 ProjectKey : projectKey ,
71123 Component : longComponent500 ,
72124 Rule : "java:S3776" ,
73125 Severity : "CRITICAL" ,
74- }
75- result := dataflowTester .Db .Create (testIssue )
76- assert .NoError (t , result .Error , "inserting issue with long component should succeed" )
126+ })
127+ require .NoError (t , result .Error )
77128
78- // Insert code blocks with long component paths
79129 codeBlocks := []* models.SonarqubeIssueCodeBlock {
80130 {
81131 ConnectionId : 1 ,
82132 Id : "test-long-component-block-256" ,
83133 IssueKey : issueKey ,
84134 Component : longComponent256 ,
85- StartLine : 10 ,
86- EndLine : 20 ,
87- StartOffset : 0 ,
88- EndOffset : 50 ,
89- Msg : "Test message for component > 256 chars" ,
135+ Msg : "component longer than 256 characters" ,
90136 },
91137 {
92138 ConnectionId : 1 ,
93139 Id : "test-long-component-block-500" ,
94140 IssueKey : issueKey ,
95141 Component : longComponent500 ,
96- StartLine : 42 ,
97- EndLine : 42 ,
98- StartOffset : 5 ,
99- EndOffset : 80 ,
100- Msg : "Test message for component > 500 chars" ,
142+ Msg : "component longer than 500 characters" ,
101143 },
102144 }
103145 for _ , block := range codeBlocks {
104- result := dataflowTester .Db .Create (block )
105- assert .NoError (t , result .Error , "inserting code block with long component should succeed (tool layer)" )
146+ require .NoError (t , dataflowTester .Db .Create (block ).Error )
106147 }
107148
108- // Run the converter subtask - this is where the original error occurred
109- taskData := & tasks.SonarqubeTaskData {
149+ dataflowTester .Subtask (tasks .ConvertIssueCodeBlocksMeta , & tasks.SonarqubeTaskData {
110150 Options : & tasks.SonarqubeOptions {
111151 ConnectionId : 1 ,
112152 ProjectKey : projectKey ,
113153 },
114154 TaskStartTime : time .Now (),
115- }
116-
117- // This would fail with the old schema:
118- // Error 1406 (22001): Data too long for column 'component' at row N
119- dataflowTester .Subtask (tasks .ConvertIssueCodeBlocksMeta , taskData )
155+ })
120156
121- // Verify the converted domain layer records have the full component path
122157 var domainBlocks []codequality.CqIssueCodeBlock
123- result = dataflowTester .Db .Find (& domainBlocks )
124- assert .NoError (t , result .Error )
125- assert .GreaterOrEqual (t , len (domainBlocks ), 2 , "should have at least 2 converted code blocks" )
126-
127- // Verify the long component values were preserved without truncation
128- foundLong256 := false
129- foundLong500 := false
130- for _ , block := range domainBlocks {
131- if block .Component == longComponent256 {
132- foundLong256 = true
133- assert .Equal (t , "Test message for component > 256 chars" , block .Msg )
134- }
135- if block .Component == longComponent500 {
136- foundLong500 = true
137- assert .Equal (t , "Test message for component > 500 chars" , block .Msg )
138- }
139- }
140- assert .True (t , foundLong256 , "domain layer should contain the 256+ char component without truncation" )
141- assert .True (t , foundLong500 , "domain layer should contain the 500+ char component without truncation" )
158+ require .NoError (t , dataflowTester .Db .Find (& domainBlocks ).Error )
159+ require .Len (t , domainBlocks , 2 )
160+ require .ElementsMatch (t ,
161+ []string {longComponent256 , longComponent500 },
162+ []string {domainBlocks [0 ].Component , domainBlocks [1 ].Component },
163+ )
142164
143- // Also verify the tool layer stored the values correctly
144165 var toolBlocks []models.SonarqubeIssueCodeBlock
145- result = dataflowTester .Db .Where ("connection_id = ? AND issue_key = ?" , 1 , issueKey ).Find (& toolBlocks )
146- assert .NoError (t , result .Error )
147- assert .Equal (t , 2 , len (toolBlocks ))
148- for _ , block := range toolBlocks {
149- assert .True (t , len (block .Component ) > 256 ,
150- "tool layer component should not be truncated" )
151- }
166+ require .NoError (t , dataflowTester .Db .Where (
167+ "connection_id = ? AND issue_key = ?" , 1 , issueKey ,
168+ ).Find (& toolBlocks ).Error )
169+ require .Len (t , toolBlocks , 2 )
170+ require .ElementsMatch (t ,
171+ []string {longComponent256 , longComponent500 },
172+ []string {toolBlocks [0 ].Component , toolBlocks [1 ].Component },
173+ )
174+ }
152175
153- // Cleanup - ignore NoPKModel fields in verification
154- _ = common.NoPKModel {}
176+ func runMigration (t * testing.T , scripts []plugin.MigrationScript , name string , basicRes * implcontext.DefaultBasicRes ) {
177+ t .Helper ()
178+ for _ , script := range scripts {
179+ if script .Name () == name {
180+ require .NoError (t , script .Up (basicRes ))
181+ return
182+ }
183+ }
184+ require .Fail (t , "migration is not registered" , name )
155185}
156186
187+ func assertTextColumnWithoutIndex (t * testing.T , dataflowTester * e2ehelper.DataFlowTester , table string ) {
188+ t .Helper ()
189+ columnTypes , err := dataflowTester .Db .Migrator ().ColumnTypes (table )
190+ require .NoError (t , err )
191+ for _ , columnType := range columnTypes {
192+ if columnType .Name () == "component" {
193+ require .Contains (t , strings .ToLower (columnType .DatabaseTypeName ()), "text" )
194+ require .False (t , dataflowTester .Db .Migrator ().HasIndex (table , "idx_" + table + "_component" ))
195+ return
196+ }
197+ }
198+ require .Fail (t , "component column not found" , table )
199+ }
0 commit comments