package virtual import ( "context" "errors" "regexp" "testing" "git.unkin.net/unkin/artifactapi/pkg/models" ) func fakeEngine(members map[string]*RPMMember) *Engine { return &Engine{ getRemote: func(_ context.Context, name string) (*models.Remote, error) { return &models.Remote{Name: name, RepoType: models.RepoTypeRemote}, nil }, rpmMember: func(_ context.Context, name string) (*RPMMember, error) { if m := members[name]; m != nil { return m, nil } return nil, errors.New("upstream down") }, } } func rpmVirt(members ...string) models.Virtual { return models.Virtual{Name: "v", PackageType: models.PackageRPM, Members: members} } func TestFetchRPMFailsClosed(t *testing.T) { e := fakeEngine(map[string]*RPMMember{ "a": {RemoteName: "a", Data: map[string][]byte{"primary": primaryXML(primaryPkgXML("foo", "1", "aaa", "foo.rpm"))}}, }) _, _, err := e.Fetch(context.Background(), rpmVirt("a", "b"), "repodata/repomd.xml", "") if err == nil || errors.Is(err, ErrNotFound) { t.Fatalf("want upstream error with a member down, got %v", err) } } func TestFetchRPMDataSurvivesMemberChange(t *testing.T) { m := &RPMMember{RemoteName: "a", Data: map[string][]byte{"primary": primaryXML(primaryPkgXML("foo", "1", "aaa", "foo.rpm"))}} e := fakeEngine(map[string]*RPMMember{"a": m}) virt := rpmVirt("a") repomd, _, err := e.Fetch(context.Background(), virt, "repodata/repomd.xml", "") if err != nil { t.Fatal(err) } m.Data = map[string][]byte{"primary": primaryXML(primaryPkgXML("foo", "2", "bbb", "foo2.rpm"))} href := regexp.MustCompile(`repodata/[0-9a-f]+-primary\.xml\.gz`).Find(repomd) body, _, err := e.Fetch(context.Background(), virt, string(href), "") if err != nil { t.Fatalf("data from previous repomd must stay resolvable: %v", err) } if got := string(gunzip(t, body)); !regexp.MustCompile(`ver="1"`).MatchString(got) { t.Fatalf("served wrong primary:\n%s", got) } newMD, _, err := e.Fetch(context.Background(), virt, "repodata/repomd.xml", "") if err != nil || string(newMD) == string(repomd) { t.Fatalf("repomd should reflect the member change (err %v)", err) } } func TestMemberRedirect(t *testing.T) { e := fakeEngine(nil) virt := rpmVirt("gh", "other") for _, tc := range []struct { path, query, want string ok bool err error }{ {path: "gh/o/r/releases/download/v1/c++-1.rpm", query: "x=1", ok: true, want: "http://h/api/v1/remote/gh/o/r/releases/download/v1/c++-1.rpm?x=1"}, {path: "gh/a%20b/c%3Fd.rpm", ok: true, want: "http://h/api/v1/remote/gh/a%20b/c%3Fd.rpm"}, {path: "gh/../../remote/other/x", err: ErrBadPath}, {path: "gh/%2e%2e/%2e%2e/remote/other/x", err: ErrBadPath}, {path: "gh/a/./b", err: ErrBadPath}, {path: "gh//etc/passwd", err: ErrBadPath}, {path: "repodata/repomd.xml"}, {path: "nope/x.rpm"}, } { got, ok, err := e.MemberRedirect(context.Background(), virt, tc.path, tc.query, "http://h/") if got != tc.want || ok != tc.ok || !errors.Is(err, tc.err) { t.Errorf("%s: got (%q, %v, %v), want (%q, %v, %v)", tc.path, got, ok, err, tc.want, tc.ok, tc.err) } } }