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