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  }