infra/blueprint-test/pkg/golden/golden.go (118 lines of code) (raw):

/** * Copyright 2022-2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless assertd by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package golden helps manage goldenfiles. package golden import ( "fmt" "os" "path" "strings" "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud" "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/utils" "github.com/mitchellh/go-testing-interface" "github.com/stretchr/testify/assert" "github.com/tidwall/gjson" ) const ( gfDir = "testdata" gfPerms = 0755 gfUpdateEnvVar = "UPDATE_GOLDEN" ) type GoldenFile struct { dir string fileName string sanitizers []Sanitizer t testing.TB } type Sanitizer func(string) string // StringSanitizer replaces all occurrences of old string with new string func StringSanitizer(old, new string) Sanitizer { return func(s string) string { return strings.ReplaceAll(s, old, new) } } // ProjectIDSanitizer replaces all occurrences of current gcloud project ID with PROJECT_ID string func ProjectIDSanitizer(t testing.TB) Sanitizer { return func(s string) string { projectID := gcloud.Run(t, "config get-value project") if projectID.String() == "[]" { t.Logf("no project ID currently set, skipping ProjectIDSanitizer: %s", projectID.String()) return s } return strings.ReplaceAll(s, projectID.String(), "PROJECT_ID") } } type goldenFileOption func(*GoldenFile) func WithDir(dir string) goldenFileOption { return func(g *GoldenFile) { g.dir = dir } } func WithFileName(fn string) goldenFileOption { return func(g *GoldenFile) { g.fileName = fn } } func WithSanitizer(s Sanitizer) goldenFileOption { return func(g *GoldenFile) { g.sanitizers = append(g.sanitizers, s) } } func WithStringSanitizer(old, new string) goldenFileOption { return func(g *GoldenFile) { g.sanitizers = append(g.sanitizers, StringSanitizer(old, new)) } } func NewOrUpdate(t testing.TB, data string, opts ...goldenFileOption) *GoldenFile { g := &GoldenFile{ dir: gfDir, fileName: fmt.Sprintf("%s.json", strings.ReplaceAll(t.Name(), "/", "-")), sanitizers: []Sanitizer{ProjectIDSanitizer(t)}, t: t, } for _, opt := range opts { opt(g) } g.update(data) return g } // update updates goldenfile data iff gfUpdateEnvVar is true func (g *GoldenFile) update(data string) { // exit early if gfUpdateEnvVar is not set or true if strings.ToLower(os.Getenv(gfUpdateEnvVar)) != "true" { return } fp := g.GetName() err := os.MkdirAll(path.Dir(fp), gfPerms) if err != nil { g.t.Fatalf("error updating result: %v", err) } // apply sanitizers on data data = g.ApplySanitizers(data) err = os.WriteFile(fp, []byte(data), gfPerms) if err != nil { g.t.Fatalf("error updating result: %v", err) } } // GetName return path of the goldenfile func (g *GoldenFile) GetName() string { return path.Join(g.dir, g.fileName) } // ApplySanitizers returns sanitized string func (g *GoldenFile) ApplySanitizers(s string) string { for _, sanitizer := range g.sanitizers { s = sanitizer(s) } return s } // GetSanitizedJSON returns sanitizes and returns JSON result func (g *GoldenFile) GetSanitizedJSON(s gjson.Result) gjson.Result { resultStr := s.String() resultStr = g.ApplySanitizers(resultStr) return utils.ParseJSONResult(g.t, resultStr) } // GetJSON returns goldenfile as parsed json func (g *GoldenFile) GetJSON() gjson.Result { return utils.LoadJSON(g.t, g.GetName()) } // JSONEq asserts that json content in jsonPath for got and goldenfile is the same func (g *GoldenFile) JSONEq(a *assert.Assertions, got gjson.Result, jsonPath string) { gf := g.GetJSON() getPath := fmt.Sprintf("%s|@ugly", jsonPath) gotData := g.ApplySanitizers(got.Get(getPath).String()) gfData := gf.Get(getPath).String() a.Equalf(gfData, gotData, "For path %q expected %q to match fixture %q", jsonPath, gotData, gfData) } // JSONPathEqs asserts that json content in jsonPaths for got and goldenfile are the same func (g *GoldenFile) JSONPathEqs(a *assert.Assertions, got gjson.Result, jsonPaths []string) { for i, jsonPath := range jsonPaths { g.t.Logf("Checking (%d of %d) JSON paths: %s", i, len(jsonPaths), jsonPath) g.JSONEq(a, got, jsonPath) } }