-
Notifications
You must be signed in to change notification settings - Fork 42
/
helpers.go
250 lines (203 loc) · 7.38 KB
/
helpers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path"
"testing"
"time"
"github.com/BurntSushi/toml"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/distro"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func externalRequest(method, path, body string) *http.Response {
client := http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", "/run/weldr/api.socket")
},
},
}
req, err := http.NewRequest(method, "http://localhost"+path, bytes.NewReader([]byte(body)))
if err != nil {
panic(err)
}
if method == "POST" {
req.Header.Set("Content-Type", "application/json")
}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
return resp
}
func internalRequest(api http.Handler, method, path, body string) *http.Response {
req := httptest.NewRequest(method, path, bytes.NewReader([]byte(body)))
req.Header.Set("Content-Type", "application/json")
resp := httptest.NewRecorder()
api.ServeHTTP(resp, req)
return resp.Result()
}
func SendHTTP(api http.Handler, external bool, method, path, body string) *http.Response {
if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 {
if !external {
return nil
}
return externalRequest(method, path, body)
} else {
return internalRequest(api, method, path, body)
}
}
// this function serves to drop fields that shouldn't be tested from the unmarshalled json objects
func dropFields(obj interface{}, fields ...string) {
switch v := obj.(type) {
// if the interface type is a map attempt to delete the fields
case map[string]interface{}:
for _, field := range fields {
delete(v, field)
}
// call dropFields on the remaining elements since they may contain a map containing the field
for _, val := range v {
dropFields(val, fields...)
}
// if the type is a list of interfaces call dropFields on each interface
case []interface{}:
for _, element := range v {
dropFields(element, fields...)
}
default:
return
}
}
func TestRoute(t *testing.T, api http.Handler, external bool, method, path, body string, expectedStatus int, expectedJSON string, ignoreFields ...string) {
t.Helper()
_ = TestRouteWithReply(t, api, external, method, path, body, expectedStatus, expectedJSON, ignoreFields...)
}
// TestRouteWithReply tests the given API endpoint and if the test passes, it returns the raw JSON reply.
func TestRouteWithReply(t *testing.T, api http.Handler, external bool, method, path, body string, expectedStatus int, expectedJSON string, ignoreFields ...string) (replyJSON []byte) {
t.Helper()
resp := SendHTTP(api, external, method, path, body)
if resp == nil {
t.Skip("This test is for internal testing only")
}
var err error
replyJSON, err = io.ReadAll(resp.Body)
require.NoErrorf(t, err, "%s: could not read response body", path)
assert.Equalf(t, expectedStatus, resp.StatusCode, "SendHTTP failed for path %s: %v", path, string(replyJSON))
if expectedJSON == "" {
require.Lenf(t, replyJSON, 0, "%s: expected no response body, but got:\n%s", path, replyJSON)
}
if expectedJSON == "?" {
return
}
var reply, expected interface{}
err = json.Unmarshal(replyJSON, &reply)
require.NoErrorf(t, err, "%s: json.Unmarshal failed for\n%s", path, string(replyJSON))
if expectedJSON == "*" {
return
}
err = json.Unmarshal([]byte(expectedJSON), &expected)
require.NoErrorf(t, err, "%s: expected JSON is invalid", path)
dropFields(reply, ignoreFields...)
dropFields(expected, ignoreFields...)
require.Equal(t, expected, reply)
return
}
func TestTOMLRoute(t *testing.T, api http.Handler, external bool, method, path, body string, expectedStatus int, expectedTOML string, ignoreFields ...string) {
t.Helper()
resp := SendHTTP(api, external, method, path, body)
if resp == nil {
t.Skip("This test is for internal testing only")
}
replyTOML, err := io.ReadAll(resp.Body)
require.NoErrorf(t, err, "%s: could not read response body", path)
assert.Equalf(t, expectedStatus, resp.StatusCode, "SendHTTP failed for path %s: %v", path, string(replyTOML))
if expectedTOML == "" {
require.Lenf(t, replyTOML, 0, "%s: expected no response body, but got:\n%s", path, replyTOML)
}
var reply, expected interface{}
err = toml.Unmarshal(replyTOML, &reply)
require.NoErrorf(t, err, "%s: json.Unmarshal failed for\n%s", path, string(replyTOML))
if expectedTOML == "*" {
return
}
err = toml.Unmarshal([]byte(expectedTOML), &expected)
require.NoErrorf(t, err, "%s: expected TOML is invalid", path)
dropFields(reply, ignoreFields...)
dropFields(expected, ignoreFields...)
require.Equal(t, expected, reply)
}
func TestNonJsonRoute(t *testing.T, api http.Handler, external bool, method, path, body string, expectedStatus int, expectedResponse string) {
response := SendHTTP(api, external, method, path, body)
assert.Equalf(t, expectedStatus, response.StatusCode, "%s: status mismatch", path)
responseBodyBytes, err := io.ReadAll(response.Body)
require.NoErrorf(t, err, "%s: could not read response body", path)
responseBody := string(responseBodyBytes)
require.Equalf(t, expectedResponse, responseBody, "%s: body mismatch", path)
}
func IgnoreDates() cmp.Option {
return cmp.Comparer(func(a, b time.Time) bool { return true })
}
func IgnoreUuids() cmp.Option {
return cmp.Comparer(func(a, b uuid.UUID) bool { return true })
}
func Ignore(what string) cmp.Option {
return cmp.FilterPath(func(p cmp.Path) bool { return p.String() == what }, cmp.Ignore())
}
// CompareImageType considers two image type objects equal if and only if the names of their distro/arch/imagetype
// are. The thinking is that the objects are static, and resolving by these three keys should always give equivalent
// objects. Whether we actually have object equality, is an implementation detail, so we don't want to rely on that.
func CompareImageTypes() cmp.Option {
return cmp.Comparer(func(x, y distro.ImageType) bool {
return x.Name() == y.Name() &&
x.Arch().Name() == y.Arch().Name() &&
x.Arch().Distro().Name() == y.Arch().Distro().Name()
})
}
// Create a temporary repository
func SetUpTemporaryRepository() (string, error) {
dir, err := os.MkdirTemp("/tmp", "osbuild-composer-test-")
if err != nil {
return "", err
}
// There's no potential command injection vector here
/* #nosec G204 */
cmd := exec.Command("createrepo_c", path.Join(dir))
err = cmd.Start()
if err != nil {
return "", err
}
err = cmd.Wait()
if err != nil {
return "", err
}
return dir, nil
}
// Remove the temporary repository
func TearDownTemporaryRepository(dir string) error {
return os.RemoveAll(dir)
}
// GenerateCIArtifactName generates a new identifier for CI artifacts which is based
// on environment variables specified by Jenkins
// note: in case of migration to sth else like Github Actions, change it to whatever variables GH Action provides
func GenerateCIArtifactName(prefix string) (string, error) {
distroCode := os.Getenv("DISTRO_CODE")
branchName := os.Getenv("BRANCH_NAME")
buildId := os.Getenv("BUILD_ID")
if branchName == "" || buildId == "" || distroCode == "" {
return "", fmt.Errorf("The environment variables must specify BRANCH_NAME, BUILD_ID, and DISTRO_CODE")
}
arch := arch.Current().String()
return fmt.Sprintf("%s%s-%s-%s-%s", prefix, distroCode, arch, branchName, buildId), nil
}