• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mindersec / minder / 23865507914

01 Apr 2026 06:56PM UTC coverage: 58.106% (-0.2%) from 58.306%
23865507914

Pull #6233

github

web-flow
Merge 19a25c356 into cac62d57e
Pull Request #6233: test(fixture): cover yaml.Unmarshal error path in Parse

8 of 127 new or added lines in 2 files covered. (6.3%)

19229 of 33093 relevant lines covered (58.11%)

35.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.34
/pkg/testing/testing.go
1
// SPDX-FileCopyrightText: Copyright 2026 The Minder Authors
2
// SPDX-License-Identifier: Apache-2.0
3

4
// Package testing provides a rule-test fixture framework for Minder.
5
// It lets authors declare test cases (expected provider responses, git
6
// files, etc.) in YAML and validate them locally before pushing.
7
package testing
8

9
import (
10
        "fmt"
11
        "net/http"
12
        "net/url"
13
        "os"
14
        "testing"
15

16
        "github.com/go-git/go-billy/v5"
17
        "github.com/go-git/go-billy/v5/memfs"
18
        "gopkg.in/yaml.v3"
19
)
20

21
// Fixture is the top-level structure parsed from a rule-test YAML file.
22
type Fixture struct {
23
        Version   string     `yaml:"version"`
24
        RuleName  string     `yaml:"rule_name"`
25
        TestCases []TestCase `yaml:"test_cases"`
26
}
27

28
// TestCase describes one scenario inside a fixture file.
29
type TestCase struct {
30
        Name       string             `yaml:"name"`
31
        Expect     string             `yaml:"expect"`
32
        SkipReason string             `yaml:"skip_reason"`
33
        MockData   ProviderMockConfig `yaml:"mock_data"`
34
}
35

36
// ProviderMockConfig holds mock data for all provider types.
37
type ProviderMockConfig struct {
38
        GitFiles            map[string]string           `yaml:"git_files"`
39
        HTTPResponses       map[string]HTTPResponseMock `yaml:"http_responses"`
40
        DataSourceResponses map[string]HTTPResponseMock `yaml:"data_source_responses"`
41
}
42

43
// HTTPResponseMock defines a canned HTTP response for a given URL.
44
type HTTPResponseMock struct {
45
        StatusCode int    `yaml:"status_code"`
46
        Body       string `yaml:"body"`
47
}
48

49
// Mocks holds the constructed mock objects for a single test case.
50
type Mocks struct {
51
        GitFilesystem    billy.Filesystem
52
        HTTPClient       *http.Client
53
        DataSourceClient *http.Client
54
}
55

56
// Result records the outcome of a single test case after DryRun.
57
type Result struct {
58
        Name       string
59
        Err        error
60
        Skipped    bool
61
        SkipReason string
62
}
63

64
// Parse reads a fixture YAML file from disk and returns the parsed Fixture.
65
func Parse(path string) (*Fixture, error) {
2✔
66
        //nolint:gosec // path is provided by test fixtures, not user input
2✔
67
        data, err := os.ReadFile(path)
2✔
68
        if err != nil {
2✔
NEW
69
                return nil, fmt.Errorf("reading fixture %s: %w", path, err)
×
NEW
70
        }
×
71

72
        var f Fixture
2✔
73
        if err := yaml.Unmarshal(data, &f); err != nil {
4✔
74
                return nil, fmt.Errorf("parsing fixture %s: %w", path, err)
2✔
75
        }
2✔
76

NEW
77
        return &f, nil
×
78
}
79

80
// NewMockBillyFS builds an in-memory billy filesystem from a map of
81
// path → content entries.
NEW
82
func NewMockBillyFS(files map[string]string) (billy.Filesystem, error) {
×
NEW
83
        fs := memfs.New()
×
NEW
84
        for path, content := range files {
×
NEW
85
                f, err := fs.Create(path)
×
NEW
86
                if err != nil {
×
NEW
87
                        return nil, fmt.Errorf("creating %q: %w", path, err)
×
NEW
88
                }
×
NEW
89
                if _, writeErr := f.Write([]byte(content)); writeErr != nil {
×
NEW
90
                        if closeErr := f.Close(); closeErr != nil {
×
NEW
91
                                return nil, fmt.Errorf("writing %q: %w (also failed to close: %v)", path, writeErr, closeErr)
×
NEW
92
                        }
×
NEW
93
                        return nil, fmt.Errorf("writing %q: %w", path, writeErr)
×
94
                }
NEW
95
                if err := f.Close(); err != nil {
×
NEW
96
                        return nil, fmt.Errorf("closing %q: %w", path, err)
×
NEW
97
                }
×
98
        }
NEW
99
        return fs, nil
×
100
}
101

102
// BuildMocks constructs the full set of mock objects for a test case.
NEW
103
func BuildMocks(tc TestCase) (*Mocks, error) {
×
NEW
104
        m := &Mocks{}
×
NEW
105

×
NEW
106
        if len(tc.MockData.GitFiles) > 0 {
×
NEW
107
                fs, err := NewMockBillyFS(tc.MockData.GitFiles)
×
NEW
108
                if err != nil {
×
NEW
109
                        return nil, fmt.Errorf("case %q: building git filesystem: %w", tc.Name, err)
×
NEW
110
                }
×
NEW
111
                m.GitFilesystem = fs
×
NEW
112
        } else {
×
NEW
113
                m.GitFilesystem = memfs.New()
×
NEW
114
        }
×
115

NEW
116
        if len(tc.MockData.HTTPResponses) > 0 {
×
NEW
117
                m.HTTPClient = &http.Client{
×
NEW
118
                        Transport: NewMockRoundTripper(tc.MockData.HTTPResponses),
×
NEW
119
                }
×
NEW
120
        } else {
×
NEW
121
                m.HTTPClient = &http.Client{
×
NEW
122
                        Transport: NewMockRoundTripper(nil),
×
NEW
123
                }
×
NEW
124
        }
×
125

NEW
126
        if len(tc.MockData.DataSourceResponses) > 0 {
×
NEW
127
                m.DataSourceClient = &http.Client{
×
NEW
128
                        Transport: NewMockRoundTripper(tc.MockData.DataSourceResponses),
×
NEW
129
                }
×
NEW
130
        } else {
×
NEW
131
                m.DataSourceClient = &http.Client{
×
NEW
132
                        Transport: NewMockRoundTripper(nil),
×
NEW
133
                }
×
NEW
134
        }
×
135

NEW
136
        return m, nil
×
137
}
138

139
// DryRun parses a fixture file and validates each test case without
140
// actually running the rule engine.  It returns one Result per test case.
NEW
141
func DryRun(path string) ([]Result, error) {
×
NEW
142
        fixture, err := Parse(path)
×
NEW
143
        if err != nil {
×
NEW
144
                return nil, fmt.Errorf("dry-run: %w", err)
×
NEW
145
        }
×
146

NEW
147
        results := make([]Result, 0, len(fixture.TestCases))
×
NEW
148
        for _, tc := range fixture.TestCases {
×
NEW
149
                r := Result{Name: tc.Name}
×
NEW
150

×
NEW
151
                if tc.SkipReason != "" {
×
NEW
152
                        r.Skipped = true
×
NEW
153
                        r.SkipReason = tc.SkipReason
×
NEW
154
                        results = append(results, r)
×
NEW
155
                        continue
×
156
                }
157

NEW
158
                _, buildErr := BuildMocks(tc)
×
NEW
159
                if buildErr != nil {
×
NEW
160
                        r.Err = buildErr
×
NEW
161
                        results = append(results, r)
×
NEW
162
                        continue
×
163
                }
164

NEW
165
                results = append(results, r)
×
166
        }
167

NEW
168
        return results, nil
×
169
}
170

171
// VerifyGitFiles checks that every file declared in the map exists in the
172
// given billy filesystem.
NEW
173
func VerifyGitFiles(fs billy.Filesystem, files map[string]string) error {
×
NEW
174
        for path := range files {
×
NEW
175
                if _, err := fs.Stat(path); err != nil {
×
NEW
176
                        return fmt.Errorf("file %q not found in filesystem: %w", path, err)
×
NEW
177
                }
×
178
        }
NEW
179
        return nil
×
180
}
181

182
// VerifyURLs validates that every key in the provided maps is a valid URL.
NEW
183
func VerifyURLs(maps ...map[string]HTTPResponseMock) error {
×
NEW
184
        for _, m := range maps {
×
NEW
185
                for rawURL := range m {
×
NEW
186
                        if _, err := url.ParseRequestURI(rawURL); err != nil {
×
NEW
187
                                return fmt.Errorf("invalid URL %q: %w", rawURL, err)
×
NEW
188
                        }
×
189
                }
190
        }
NEW
191
        return nil
×
192
}
193

194
// WriteTempFixture is a test helper that writes YAML content to a temporary
195
// file and returns its path.
NEW
196
func WriteTempFixture(t *testing.T, content string) string {
×
NEW
197
        t.Helper()
×
NEW
198

×
NEW
199
        dir := t.TempDir()
×
NEW
200
        path := dir + "/fixture.yaml"
×
NEW
201
        if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
×
NEW
202
                t.Fatalf("writing temp fixture: %v", err)
×
NEW
203
        }
×
NEW
204
        return path
×
205
}
206

207
// MustParseURL is a test helper that parses a URL or fails the test.
NEW
208
func MustParseURL(t *testing.T, rawURL string) *url.URL {
×
NEW
209
        t.Helper()
×
NEW
210

×
NEW
211
        u, err := url.Parse(rawURL)
×
NEW
212
        if err != nil {
×
NEW
213
                t.Fatalf("parsing URL %q: %v", rawURL, err)
×
NEW
214
        }
×
NEW
215
        return u
×
216
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc