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  }