| // Copyright 2026 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package api |
| |
| import ( |
| "encoding/json" |
| "net/http" |
| "net/http/httptest" |
| "testing" |
| |
| "golang.org/x/pkgsite/internal" |
| "golang.org/x/pkgsite/internal/osv" |
| "golang.org/x/pkgsite/internal/testing/fakedatasource" |
| "golang.org/x/pkgsite/internal/vuln" |
| ) |
| |
| // Tests here do not depend on postgres. |
| // See internal/tests/api for the ones that do. |
| |
| func TestServeVulnerabilities(t *testing.T) { |
| // This test doesn't need to run against a Postgres DB, because |
| // vulnerabilities are not read from the database. |
| vc, err := vuln.NewInMemoryClient([]*osv.Entry{ |
| { |
| ID: "VULN-1", |
| Summary: "Vulnerability 1", |
| Affected: []osv.Affected{ |
| { |
| Module: osv.Module{Path: "example.com"}, |
| Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.1.0"}}}}, |
| }, |
| }, |
| }, |
| { |
| ID: "VULN-2", |
| Summary: "Vulnerability 2", |
| Affected: []osv.Affected{ |
| { |
| Module: osv.Module{Path: "example.com"}, |
| Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.1.0"}}}}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{ |
| {Path: "example.com/pkg"}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| ID: "VULN-3", |
| Summary: "Vulnerability 3", |
| Affected: []osv.Affected{ |
| { |
| Module: osv.Module{Path: "example.com"}, |
| Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.1.0"}}}}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{ |
| {Path: "example.com/other"}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| ds := fakedatasource.New() |
| insertModule := func(v string) { |
| ds.MustInsertModule(t, &internal.Module{ |
| ModuleInfo: internal.ModuleInfo{ |
| ModulePath: "example.com", |
| Version: v, |
| }, |
| Units: []*internal.Unit{ |
| { |
| UnitMeta: internal.UnitMeta{ |
| Path: "example.com", |
| ModuleInfo: internal.ModuleInfo{ |
| ModulePath: "example.com", |
| Version: v, |
| }, |
| }, |
| }, |
| { |
| UnitMeta: internal.UnitMeta{ |
| Path: "example.com/pkg", |
| ModuleInfo: internal.ModuleInfo{ |
| ModulePath: "example.com", |
| Version: v, |
| }, |
| }, |
| }, |
| { |
| UnitMeta: internal.UnitMeta{ |
| Path: "example.com/other", |
| ModuleInfo: internal.ModuleInfo{ |
| ModulePath: "example.com", |
| Version: v, |
| }, |
| }, |
| }, |
| }, |
| }) |
| } |
| insertModule("v1.0.0") |
| insertModule("v1.2.0") |
| |
| for _, test := range []struct { |
| name string |
| url string |
| wantStatus int |
| wantCount int |
| want any |
| }{ |
| { |
| name: "all vulns", |
| url: "/v1beta/vulns/example.com?version=v1.0.0", |
| wantStatus: http.StatusOK, |
| wantCount: 3, |
| }, |
| { |
| name: "no vulns", |
| url: "/v1beta/vulns/example.com?version=v1.2.0", |
| wantStatus: http.StatusOK, |
| wantCount: 0, |
| }, |
| { |
| name: "package path in vulns endpoint", |
| url: "/v1beta/vulns/example.com/pkg?version=v1.0.0", |
| wantStatus: http.StatusOK, |
| wantCount: 2, |
| }, |
| { |
| name: "another package path", |
| url: "/v1beta/vulns/example.com/other?version=v1.0.0", |
| wantStatus: http.StatusOK, |
| wantCount: 2, |
| }, |
| } { |
| t.Run(test.name, func(t *testing.T) { |
| r := httptest.NewRequest("GET", test.url, nil) |
| w := httptest.NewRecorder() |
| |
| if err := ServeVulnerabilities(vc)(w, r, ds); err != nil { |
| ServeError(w, r, err) |
| } |
| |
| if w.Code != test.wantStatus { |
| t.Errorf("status = %d, want %d", w.Code, test.wantStatus) |
| } |
| |
| if test.wantStatus == http.StatusOK { |
| var got PaginatedResponse[Vulnerability] |
| if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil { |
| t.Fatalf("json.Unmarshal: %v", err) |
| } |
| if len(got.Items) != test.wantCount { |
| t.Errorf("count = %d, want %d", len(got.Items), test.wantCount) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestCacheControl(t *testing.T) { |
| // This test doesn't need to run against a Postgres DB, because |
| // it's concerned only with headers. |
| ds := fakedatasource.New() |
| const modulePath = "example.com" |
| for _, v := range []string{"v1.0.0", "master"} { |
| ds.MustInsertModule(t, &internal.Module{ |
| ModuleInfo: internal.ModuleInfo{ |
| ModulePath: modulePath, |
| Version: v, |
| }, |
| Units: []*internal.Unit{{ |
| UnitMeta: internal.UnitMeta{ |
| Path: modulePath, |
| ModuleInfo: internal.ModuleInfo{ |
| ModulePath: modulePath, |
| Version: v, |
| }, |
| }, |
| }}, |
| }) |
| } |
| |
| for _, test := range []struct { |
| version string |
| want string |
| }{ |
| {"v1.0.0", "public, max-age=10800"}, |
| {"latest", "public, max-age=3600"}, |
| {"master", "public, max-age=3600"}, |
| {"", "public, max-age=3600"}, |
| } { |
| t.Run(test.version, func(t *testing.T) { |
| url := "/v1beta/module/" + modulePath |
| if test.version != "" { |
| url += "?version=" + test.version |
| } |
| r := httptest.NewRequest("GET", url, nil) |
| w := httptest.NewRecorder() |
| |
| if err := ServeModule(w, r, ds); err != nil { |
| t.Fatal(err) |
| } |
| |
| if w.Code != http.StatusOK { |
| t.Fatalf("status = %d, want %d", w.Code, http.StatusOK) |
| } |
| |
| got := w.Header().Get("Cache-Control") |
| if got != test.want { |
| t.Errorf("Cache-Control = %q, want %q", got, test.want) |
| } |
| }) |
| } |
| } |