Skip to content

Commit 1249842

Browse files
committed
add test that checks for orphan cassettes
1 parent 42afe41 commit 1249842

File tree

2 files changed

+98
-13
lines changed

2 files changed

+98
-13
lines changed

internal/acctest/validate_cassettes_test.go

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,27 @@ package acctest_test
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
7+
"go/ast"
8+
"go/parser"
9+
"go/token"
610
"io/fs"
711
"net/http"
812
"path/filepath"
13+
"sort"
914
"strings"
1015
"testing"
1116

17+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
1218
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/mnq"
1319
"github.com/stretchr/testify/assert"
1420
"github.com/stretchr/testify/require"
1521
"gopkg.in/dnaeon/go-vcr.v3/cassette"
1622
)
1723

24+
const servicesDir = "../services"
25+
1826
func exceptionsCassettesCases() map[string]struct{} {
1927
return map[string]struct{}{
2028
"../services/mnq/testdata/sns-topic-basic.cassette.yaml": {},
@@ -35,16 +43,16 @@ func exceptionsCassettesCases() map[string]struct{} {
3543
}
3644

3745
// getTestFiles returns a map of cassettes files
38-
func getTestFiles() (map[string]struct{}, error) {
46+
func getTestFiles(includeExceptions bool) (map[string]struct{}, error) {
3947
filesMap := make(map[string]struct{})
4048
exceptions := exceptionsCassettesCases()
4149

42-
err := filepath.WalkDir("../services", func(path string, _ fs.DirEntry, _ error) error {
43-
isCassette := strings.Contains(path, "cassette")
44-
_, isException := exceptions[path]
45-
46-
if isCassette && !isException {
47-
filesMap[fileNameWithoutExtSuffix(path)] = struct{}{}
50+
err := filepath.WalkDir(servicesDir, func(path string, _ fs.DirEntry, _ error) error {
51+
if isCassette := strings.Contains(path, "cassette"); isCassette {
52+
_, isException := exceptions[path]
53+
if !isException || includeExceptions {
54+
filesMap[fileNameWithoutExtSuffix(path)] = struct{}{}
55+
}
4856
}
4957

5058
return nil
@@ -57,7 +65,7 @@ func getTestFiles() (map[string]struct{}, error) {
5765
}
5866

5967
func TestAccCassettes_Validator(t *testing.T) {
60-
paths, err := getTestFiles()
68+
paths, err := getTestFiles(false)
6169
require.NoError(t, err)
6270

6371
for path := range paths {
@@ -129,3 +137,76 @@ func isTransientStateError(i *cassette.Interaction) bool {
129137

130138
return scwError.Type == "transient_state"
131139
}
140+
141+
func listAccTestFunctions() (map[string]string, error) {
142+
fset := token.NewFileSet()
143+
testFuncs := map[string]string{}
144+
145+
err := filepath.WalkDir(servicesDir, func(path string, _ fs.DirEntry, _ error) error {
146+
if strings.HasSuffix(path, "_test.go") {
147+
pkgFolder := filepath.Base(filepath.Dir(path))
148+
149+
node, err := parser.ParseFile(fset, path, nil, 0)
150+
if err != nil {
151+
return err
152+
}
153+
154+
for _, decl := range node.Decls {
155+
if fn, ok := decl.(*ast.FuncDecl); ok {
156+
if strings.HasPrefix(fn.Name.Name, "Test") && fn.Name.Name != "TestMain" && fn.Recv == nil {
157+
expectedCassettePath := fmt.Sprintf("%s/%s", servicesDir, acctest.BuildCassetteName(fn.Name.Name, pkgFolder, ".cassette"))
158+
testFuncs[expectedCassettePath] = fmt.Sprintf("%s/%s", pkgFolder, fn.Name.Name)
159+
}
160+
}
161+
}
162+
}
163+
164+
return nil
165+
})
166+
167+
return testFuncs, err
168+
}
169+
170+
func TestAccCassettes_CheckOrphans(t *testing.T) {
171+
// List actual cassettes
172+
actualCassettesPaths, err := getTestFiles(true)
173+
if err != nil {
174+
t.Fatalf("Failed to list cassettes: %v", err)
175+
}
176+
177+
// List actual acceptance tests functions and their expected cassettes' paths
178+
expectedCassettesPaths, err := listAccTestFunctions()
179+
if err != nil {
180+
t.Fatalf("Failed to list acceptance tests: %v", err)
181+
}
182+
183+
testWithNoCassetteErrs := []string(nil)
184+
cassetteWithNoTestErrs := []error(nil)
185+
186+
// Look for tests with no matching cassette
187+
for expectedCassettePath, testName := range expectedCassettesPaths {
188+
if _, ok := actualCassettesPaths[expectedCassettePath]; !ok {
189+
testWithNoCassetteErrs = append(testWithNoCassetteErrs, fmt.Sprintf("- %s has no matching cassette", testName))
190+
}
191+
}
192+
193+
// Look for cassettes with no matching test
194+
for actualCassettePath := range actualCassettesPaths {
195+
if _, ok := expectedCassettesPaths[actualCassettePath]; !ok {
196+
cassetteWithNoTestErrs = append(cassetteWithNoTestErrs, fmt.Errorf("+ cassette [%s] has no matching test", actualCassettePath))
197+
}
198+
}
199+
200+
// Print results:
201+
// If a cassette has no test, it should result in an error, but if a test has no cassette, it should only result in
202+
// a warning (e.g. for tests that are currently skipped and which cassette had to be removed because of a 500, or else)
203+
sort.Strings(testWithNoCassetteErrs)
204+
t.Log("WARNING:\n", strings.Join(testWithNoCassetteErrs, "\n"))
205+
206+
if len(cassetteWithNoTestErrs) > 0 {
207+
sort.Slice(cassetteWithNoTestErrs, func(i, j int) bool {
208+
return cassetteWithNoTestErrs[i].Error() < cassetteWithNoTestErrs[j].Error()
209+
})
210+
t.Error(errors.Join(cassetteWithNoTestErrs...))
211+
}
212+
}

internal/acctest/vcr.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,11 @@ func removeKeyRecursive(m map[string]any, key string) {
6262
}
6363
}
6464

65-
// getTestFilePath returns a valid filename path based on the go test name and suffix. (Take care of non fs friendly char)
66-
func getTestFilePath(t *testing.T, pkgFolder string, suffix string) string {
67-
t.Helper()
68-
65+
func BuildCassetteName(testName string, pkgFolder string, suffix string) string {
6966
specialChars := regexp.MustCompile(`[\\?%*:|"<>. ]`)
7067

7168
// Replace nested tests separators.
72-
fileName := strings.ReplaceAll(t.Name(), "/", "-")
69+
fileName := strings.ReplaceAll(testName, "/", "-")
7370

7471
fileName = strcase.ToBashArg(fileName)
7572

@@ -82,6 +79,13 @@ func getTestFilePath(t *testing.T, pkgFolder string, suffix string) string {
8279
return filepath.Join(pkgFolder, "testdata", fileName)
8380
}
8481

82+
// getTestFilePath returns a valid filename path based on the go test name and suffix. (Take care of non fs friendly char)
83+
func getTestFilePath(t *testing.T, pkgFolder string, suffix string) string {
84+
t.Helper()
85+
86+
return BuildCassetteName(t.Name(), pkgFolder, suffix)
87+
}
88+
8589
// cassetteMatcher is a custom matcher that will juste check equivalence of request bodies
8690
func cassetteBodyMatcher(request *http.Request, cassette cassette.Request) bool {
8791
if request.Body == nil || request.ContentLength == 0 {

0 commit comments

Comments
 (0)