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) } }