github.com/thetreep/go-swagger@v0.0.0-20240223100711-35af64f14f01/cmd/swagger/commands/diff_test.go (about) 1 package commands 2 3 import ( 4 "bytes" 5 "io" 6 "log" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "testing" 12 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 "github.com/thetreep/go-swagger/cmd/swagger/commands/diff" 16 "github.com/thetreep/go-swagger/cmd/swagger/commands/internal/cmdtest" 17 ) 18 19 func fixturePath(file string, parts ...string) string { 20 return filepath.Join("..", "..", "..", "fixtures", "diff", strings.Join(append([]string{file}, parts...), "")) 21 } 22 23 type testCaseData struct { 24 name string 25 oldSpec string 26 newSpec string 27 expectedError bool 28 expectedWarning bool 29 expectedLines io.ReadCloser 30 expectedFile string 31 } 32 33 // TestDiffForVariousCombinations - computes the diffs for a number 34 // of scenarios and compares the computed diff with expected diffs 35 func TestDiffForVariousCombinations(t *testing.T) { 36 37 pattern := fixturePath("*.diff.txt") 38 39 // To filter cases for debugging poke an individual case here eg "path", "enum" etc 40 // see the test cases in fixtures/diff 41 // Don't forget to remove it once you're done. 42 // (There's a test at the end to check all cases were run) 43 allTests, err := filepath.Glob(pattern) 44 require.NoErrorf(t, err, "could not find test files") 45 require.False(t, len(allTests) == 0, "could not find test files") 46 47 testCases := makeTestCases(t, allTests) 48 49 for i, tc := range testCases { 50 tc := tc 51 t.Run( 52 tc.name, func(t *testing.T) { 53 cmd := DiffCommand{} 54 cmd.Args.OldSpec = tc.oldSpec 55 cmd.Args.NewSpec = tc.newSpec 56 diffs, err := cmd.getDiffs() 57 58 if tc.expectedError { 59 // edge cases with error 60 require.Error(t, err) 61 return 62 } 63 require.NoError(t, err) 64 65 out, err, warn := diffs.ReportAllDiffs(false) 66 require.NoError(t, err) 67 68 // breaking changes reported with a warning 69 if tc.expectedWarning { 70 assert.Error(t, warn) 71 } else { 72 assert.NoError(t, warn) 73 } 74 75 if !cmdtest.AssertReadersContent(t, true, tc.expectedLines, out) { 76 t.Logf("unexpected content for fixture %q[%d] (file: %s)", tc.name, i, tc.expectedFile) 77 } 78 }, 79 ) 80 } 81 } 82 83 func TestDiffReadIgnores(t *testing.T) { 84 log.SetOutput(io.Discard) 85 defer func() { 86 log.SetOutput(os.Stdout) 87 }() 88 89 cmd := DiffCommand{ 90 IgnoreFile: fixturePath("ignoreFile.json"), 91 } 92 93 ignores, err := cmd.readIgnores() 94 require.NoError(t, err) 95 require.True(t, len(ignores) > 0) 96 97 isIn := diff.SpecDifference{ 98 DifferenceLocation: diff.DifferenceLocation{ 99 Method: "get", 100 Response: 200, 101 URL: "/b/", 102 Node: &diff.Node{Field: "Body", TypeName: "A1", IsArray: true, ChildNode: &diff.Node{Field: "personality", TypeName: "string"}}, 103 }, 104 Code: diff.DeletedEnumValue, 105 Compatibility: diff.NonBreaking, 106 DiffInfo: "crazy", 107 } 108 assert.Contains(t, ignores, isIn) 109 110 // edge case 111 cmd = DiffCommand{ 112 IgnoreFile: "/someplace/wrong", 113 } 114 _, err = cmd.readIgnores() 115 require.Error(t, err) 116 assert.Contains(t, err.Error(), "/someplace/wrong") 117 } 118 119 func TestDiffProcessIgnores(t *testing.T) { 120 log.SetOutput(io.Discard) 121 defer func() { 122 log.SetOutput(os.Stdout) 123 }() 124 125 const namePart = "enum" 126 tc := testCaseData{ 127 name: namePart, 128 oldSpec: fixturePath(namePart, ".v1.json"), 129 newSpec: fixturePath(namePart, ".v2.json"), 130 expectedLines: linesInFile(t, fixturePath("ignoreDiffs.json")), 131 } 132 133 reportFile, err := os.CreateTemp("", "report.txt") 134 require.NoError(t, err) 135 defer func() { 136 _ = os.Remove(reportFile.Name()) 137 }() 138 139 cmd := DiffCommand{ 140 Format: "json", 141 IgnoreFile: fixturePath("ignoreFile.json"), 142 Destination: reportFile.Name(), 143 } 144 cmd.Args.OldSpec = tc.oldSpec 145 cmd.Args.NewSpec = tc.newSpec 146 147 err = cmd.Execute([]string{tc.oldSpec, tc.newSpec}) 148 require.NoError(t, err) 149 150 output, err := os.Open(cmd.Destination) 151 require.NoError(t, err) 152 defer func() { 153 _ = output.Close() 154 }() 155 156 cmdtest.AssertReadersContent(t, true, tc.expectedLines, output) 157 } 158 159 func TestDiffNoArgs(t *testing.T) { 160 161 cmd := DiffCommand{ 162 Format: "json", 163 IgnoreFile: "", 164 } 165 require.Error(t, cmd.Execute(nil)) 166 167 cmd.Args.NewSpec = "x" 168 require.Error(t, cmd.Execute(nil)) 169 } 170 171 func TestDiffCannotReport(t *testing.T) { 172 log.SetOutput(io.Discard) 173 defer func() { 174 log.SetOutput(os.Stdout) 175 }() 176 177 cmd := DiffCommand{ 178 OnlyBreakingChanges: true, 179 Format: "txt", 180 IgnoreFile: "", 181 Destination: "/someplace/wrong", 182 } 183 const namePart = "enum" 184 cmd.Args.OldSpec = fixturePath(namePart, ".v1.json") 185 cmd.Args.NewSpec = fixturePath(namePart, ".v2.json") 186 err := cmd.Execute(nil) 187 require.Error(t, err) 188 assert.Contains(t, err.Error(), "/someplace/wrong") 189 } 190 191 func TestDiffOnlyBreaking(t *testing.T) { 192 log.SetOutput(io.Discard) 193 defer func() { 194 log.SetOutput(os.Stdout) 195 }() 196 197 reportDir, err := os.MkdirTemp("", "diff-reports") 198 require.NoError(t, err) 199 defer func() { 200 _ = os.RemoveAll(reportDir) 201 }() 202 txtReport := filepath.Join(reportDir, "report.txt") 203 204 cmd := DiffCommand{ 205 OnlyBreakingChanges: true, 206 Format: "txt", 207 IgnoreFile: "", 208 Destination: txtReport, 209 } 210 211 const namePart = "enum" 212 cmd.Args.OldSpec = fixturePath(namePart, ".v1.json") 213 cmd.Args.NewSpec = fixturePath(namePart, ".v2.json") 214 err = cmd.Execute(nil) 215 require.Error(t, err) 216 assert.Contains(t, err.Error(), "compatibility test FAILED") 217 218 actual, err := os.Open(txtReport) 219 require.NoError(t, err) 220 defer func() { 221 _ = actual.Close() 222 }() 223 224 expected, err := os.Open(fixturePath("enum", ".diff.breaking.txt")) 225 require.NoError(t, err) 226 defer func() { 227 _ = expected.Close() 228 }() 229 230 cmdtest.AssertReadersContent(t, true, expected, actual) 231 232 // assert stdout just the same (we do it just once, so there is no race condition on os.Stdout) 233 cmd.Destination = "stdout" 234 output, err := cmdtest.CatchStdOut(t, func() error { return cmd.Execute(nil) }) 235 require.Error(t, err) 236 assert.Contains(t, err.Error(), "compatibility test FAILED") 237 238 _, _ = expected.Seek(0, io.SeekStart) 239 result := bytes.NewBuffer(output) 240 cmdtest.AssertReadersContent(t, true, expected, result) 241 } 242 243 func fixturePart(file string) string { 244 base := filepath.Base(file) 245 parts := strings.Split(base, ".diff.txt") 246 return parts[0] 247 } 248 249 func hasFixtureBreaking(part string) bool { 250 // these fixtures expect some breaking changes 251 switch part { 252 case "enum", "kitchensink", "param", "path", "response", "refprop", "reqparam": 253 return true 254 default: 255 return false 256 } 257 } 258 259 func makeTestCases(t testing.TB, matches []string) []testCaseData { 260 testCases := make([]testCaseData, 0, len(matches)+2) 261 for _, eachFile := range matches { 262 namePart := fixturePart(eachFile) 263 264 testCases = append( 265 testCases, testCaseData{ 266 name: namePart, 267 oldSpec: fixturePath(namePart, ".v1.json"), 268 newSpec: fixturePath(namePart, ".v2.json"), 269 expectedLines: linesInFile(t, fixturePath(namePart, ".diff.txt")), 270 expectedFile: fixturePath(namePart, ".diff.txt"), // only for debugging failed tests 271 expectedWarning: hasFixtureBreaking(namePart), 272 }, 273 ) 274 } 275 276 // edge cases with errors 277 testCases = append( 278 testCases, testCaseData{ 279 name: "failure to load old spec", 280 oldSpec: "nowhere.json", 281 newSpec: fixturePath("enum", ".v2.json"), 282 expectedError: true, 283 }, 284 testCaseData{ 285 name: "failure to load new spec", 286 oldSpec: fixturePath("enum", ".v1.json"), 287 newSpec: "nowhere.json", 288 expectedError: true, 289 }, 290 ) 291 return testCases 292 } 293 294 func linesInFile(t testing.TB, fileName string) io.ReadCloser { 295 file, err := os.Open(fileName) 296 require.NoError(t, err) 297 return file 298 }