From fc90ae2382d8c33c2a9c3c739620f5a9a3394c2d Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Sun, 4 Oct 2026 01:44:19 +0200 Subject: [PATCH] test(14-06): tide reports every sidecar refusal and multipart mismatch - LoadUpstream names the file for a missing, unknown-field, wrong-version, method-less, relative-URL or out-of-range-status sidecar - WriteUpstream writes nothing for an invalid sidecar or an uncreatable directory - compareParts reports count, name, filename, content type, sha256 and value mismatches and a non-multipart body --- modules/tide/upstream_coverage_test.go | 110 +++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 modules/tide/upstream_coverage_test.go diff --git a/modules/tide/upstream_coverage_test.go b/modules/tide/upstream_coverage_test.go new file mode 100644 index 0000000..75dd905 --- /dev/null +++ b/modules/tide/upstream_coverage_test.go @@ -0,0 +1,110 @@ +package tide + +import ( + "bytes" + "errors" + "io/fs" + "mime/multipart" + "net/textproto" + "os" + "path/filepath" + "strings" + "testing" +) + +// TestUpstreamSidecarRefusals covers the refusals of the sidecar loader, +// writer and multipart comparison that the replay tests do not reach: a +// missing, malformed or invalid sidecar is an error naming the file, a +// write that cannot validate or create its directory writes nothing, every +// part mismatch is reported, and an unresolved placeholder is an error. +func TestUpstreamSidecarRefusals(t *testing.T) { + dir := t.TempDir() + if _, err := LoadUpstream(filepath.Join(dir, "missing.upstream.yaml")); !errors.Is(err, fs.ErrNotExist) { + t.Errorf("missing sidecar = %v", err) + } + write := func(name, body string) string { + path := filepath.Join(dir, name) + if err := os.WriteFile(path, []byte(body), 0o644); err != nil { + t.Fatal(err) + } + return path + } + for name, body := range map[string]string{ + "unknown-field.yaml": "version: 1\nsurprise: true\n", + "version.yaml": "version: 9\n", + "method.yaml": "version: 1\nexchanges:\n - request: {method: \" \", url: \"https://x.test/\"}\n response: {status: 200}\n", + "url.yaml": "version: 1\nexchanges:\n - request: {method: GET, url: \"/relative\"}\n response: {status: 200}\n", + "status.yaml": "version: 1\nexchanges:\n - request: {method: GET, url: \"https://x.test/\"}\n response: {status: 42}\n", + } { + if _, err := LoadUpstream(write(name, body)); err == nil || !strings.Contains(err.Error(), name) { + t.Errorf("%s = %v, want an error naming the file", name, err) + } + } + + store, err := OpenStore("") + if err != nil { + t.Fatal(err) + } + bad := UpstreamSidecar{Exchanges: []UpstreamExchange{{Request: UpstreamRequest{Method: "", URL: "https://x.test/"}, Response: UpstreamResponse{Status: 200}}}} + if err := WriteUpstream(filepath.Join(dir, "bad.upstream.yaml"), bad, store); err == nil { + t.Error("an invalid sidecar was written") + } + blocker := write("blocker", "a file, not a directory") + good := UpstreamSidecar{Exchanges: []UpstreamExchange{{Request: UpstreamRequest{Method: "GET", URL: "https://x.test/"}, Response: UpstreamResponse{Status: 200}}}} + if err := WriteUpstream(filepath.Join(blocker, "x.upstream.yaml"), good, store); err == nil { + t.Error("a sidecar was written under a file") + } + path := filepath.Join(dir, "nested", "good.upstream.yaml") + if err := WriteUpstream(path, good, store); err != nil { + t.Fatal(err) + } + if s, err := LoadUpstream(path); err != nil || s.Version != CurrentVersion { + t.Fatalf("written sidecar = %+v %v", s, err) + } + + var body bytes.Buffer + mw := multipart.NewWriter(&body) + _ = mw.WriteField("title", "Bug") + h := textproto.MIMEHeader{} + h.Set("Content-Disposition", `form-data; name="file"; filename="s.png"`) + h.Set("Content-Type", "image/png") + fw, _ := mw.CreatePart(h) + _, _ = fw.Write([]byte("png")) + _ = mw.Close() + ctype := mw.FormDataContentType() + fake := NewUpstreamFake(UpstreamSidecar{}, store) + got, err := upstreamParts(ctype, body.Bytes()) + if err != nil || len(got) != 2 { + t.Fatalf("parts %+v %v", got, err) + } + for _, c := range []struct { + name string + want []UpstreamPart + frag string + }{ + {"count", got[:1], "want 1, got 2"}, + {"name", []UpstreamPart{{Name: "other", Value: "Bug"}, got[1]}, "name: want"}, + {"filename", []UpstreamPart{got[0], {Name: "file", Filename: "x.png", ContentType: "image/png", SHA256: got[1].SHA256}}, "filename"}, + {"content type", []UpstreamPart{got[0], {Name: "file", Filename: "s.png", ContentType: "image/jpeg", SHA256: got[1].SHA256}}, "content type"}, + {"sha256", []UpstreamPart{got[0], {Name: "file", Filename: "s.png", ContentType: "image/png", SHA256: strings.Repeat("0", 64)}}, "sha256"}, + {"value", []UpstreamPart{{Name: "title", Value: "Feature"}, got[1]}, "value differs"}, + {"placeholder", []UpstreamPart{{Name: "title", Value: "{{missing}}"}, got[1]}, "unresolved placeholder"}, + } { + problems := fake.compareParts(c.want, ctype, body.Bytes()) + if len(problems) == 0 || !strings.Contains(strings.Join(problems, "; "), c.frag) { + t.Errorf("%s mismatch reported %v", c.name, problems) + } + } + if problems := fake.compareParts(got, "application/json", body.Bytes()); len(problems) != 1 || !strings.Contains(problems[0], "not multipart") { + t.Errorf("a non-multipart body = %v", problems) + } + if problems := fake.compareParts(got, ctype, body.Bytes()); len(problems) != 0 { + t.Errorf("identical parts = %v", problems) + } + if _, err := store.expandKeeping("{{absent}} and {{sha256:kept}}", isHashPlaceholder); err == nil || !strings.Contains(err.Error(), "absent") { + t.Errorf("expandKeeping with a missing name = %v", err) + } + if out, err := store.expandKeeping("plain", isHashPlaceholder); err != nil || out != "plain" { + t.Errorf("expandKeeping(plain) = %q %v", out, err) + } +}