| // Copyright 2019 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 frontend |
| |
| import ( |
| "context" |
| "net/http" |
| "net/url" |
| "sort" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "golang.org/x/pkgsite/internal" |
| "golang.org/x/pkgsite/internal/experiment" |
| "golang.org/x/pkgsite/internal/stdlib" |
| ) |
| |
| func TestExtractURLPathInfo(t *testing.T) { |
| for _, test := range []struct { |
| in string |
| want *urlPathInfo // nil => want non-nil error |
| }{ |
| {"", nil}, |
| { |
| "/a.com", |
| &urlPathInfo{ |
| fullPath: "a.com", |
| modulePath: internal.UnknownModulePath, |
| requestedVersion: internal.LatestVersion, |
| isModule: false, |
| }, |
| }, |
| { |
| "/a.com@v1.2.3", |
| &urlPathInfo{ |
| fullPath: "a.com", |
| modulePath: internal.UnknownModulePath, |
| requestedVersion: "v1.2.3", |
| isModule: false, |
| }, |
| }, |
| { |
| "/a.com@v1.2.3/b", |
| &urlPathInfo{ |
| fullPath: "a.com/b", |
| modulePath: "a.com", |
| requestedVersion: "v1.2.3", |
| isModule: false, |
| }, |
| }, |
| { |
| "/encoding/json", |
| &urlPathInfo{ |
| fullPath: "encoding/json", |
| modulePath: "std", |
| requestedVersion: internal.LatestVersion, |
| isModule: false, |
| }, |
| }, |
| { |
| "/encoding/json@go1.12", |
| &urlPathInfo{ |
| fullPath: "encoding/json", |
| modulePath: "std", |
| requestedVersion: "v1.12.0", |
| isModule: false, |
| }, |
| }, |
| { |
| "/mod/a.com", |
| &urlPathInfo{ |
| fullPath: "a.com", |
| modulePath: internal.UnknownModulePath, |
| requestedVersion: internal.LatestVersion, |
| isModule: true, |
| }, |
| }, |
| { |
| "/mod/a.com@v1.2.3", |
| &urlPathInfo{ |
| fullPath: "a.com", |
| modulePath: internal.UnknownModulePath, |
| requestedVersion: "v1.2.3", |
| isModule: true, |
| }, |
| }, |
| { |
| "/moda.com", |
| &urlPathInfo{ |
| fullPath: "moda.com", |
| modulePath: internal.UnknownModulePath, |
| requestedVersion: internal.LatestVersion, |
| isModule: false, |
| }, |
| }, |
| { |
| "/mod/a.com@v1.2.3/b", |
| &urlPathInfo{ |
| fullPath: "a.com/b", |
| modulePath: "a.com", |
| requestedVersion: "v1.2.3", |
| isModule: true, |
| }, |
| }, |
| } { |
| got, err := extractURLPathInfo(test.in) |
| if err != nil { |
| if test.want != nil { |
| t.Errorf("%q: got error %v", test.in, err) |
| } |
| continue |
| } |
| if test.want == nil { |
| t.Errorf("%q: got no error, wanted one", test.in) |
| continue |
| } |
| if diff := cmp.Diff(test.want, got, cmp.AllowUnexported(urlPathInfo{})); diff != "" { |
| t.Errorf("%q: mismatch (-want, +got):\n%s", test.in, diff) |
| } |
| } |
| } |
| |
| func TestParseDetailsURLPath(t *testing.T) { |
| testCases := []struct { |
| name, url, wantModulePath, wantFullPath, wantVersion string |
| wantErr bool |
| }{ |
| { |
| name: "latest", |
| url: "/github.com/hashicorp/vault/api", |
| wantModulePath: internal.UnknownModulePath, |
| wantFullPath: "github.com/hashicorp/vault/api", |
| wantVersion: internal.LatestVersion, |
| }, |
| { |
| name: "package at version in nested module", |
| url: "/github.com/hashicorp/vault/api@v1.0.3", |
| wantModulePath: internal.UnknownModulePath, |
| wantFullPath: "github.com/hashicorp/vault/api", |
| wantVersion: "v1.0.3", |
| }, |
| { |
| name: "package at version in parent module", |
| url: "/github.com/hashicorp/vault@v1.0.3/api", |
| wantModulePath: "github.com/hashicorp/vault", |
| wantFullPath: "github.com/hashicorp/vault/api", |
| wantVersion: "v1.0.3", |
| }, |
| { |
| name: "package at version trailing slash", |
| url: "/github.com/hashicorp/vault/api@v1.0.3/", |
| wantModulePath: internal.UnknownModulePath, |
| wantFullPath: "github.com/hashicorp/vault/api", |
| wantVersion: "v1.0.3", |
| }, |
| { |
| name: "stdlib", |
| url: "net/http", |
| wantModulePath: stdlib.ModulePath, |
| wantFullPath: "net/http", |
| wantVersion: internal.LatestVersion, |
| }, |
| { |
| name: "stdlib at version", |
| url: "net/http@go1.14", |
| wantModulePath: stdlib.ModulePath, |
| wantFullPath: "net/http", |
| wantVersion: "go1.14", |
| }, |
| { |
| name: "invalid url", |
| url: "/", |
| wantErr: true, |
| }, |
| { |
| name: "invalid url missing module", |
| url: "@v1.0.0", |
| wantErr: true, |
| }, |
| { |
| name: "explicit latest", |
| url: "/github.com/hashicorp/vault/api@latest", |
| wantErr: true, |
| }, |
| { |
| name: "split stdlib", |
| url: "/net@go1.14/http", |
| wantErr: true, |
| }, |
| } |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| u, parseErr := url.Parse(tc.url) |
| if parseErr != nil { |
| t.Errorf("url.Parse(%q): %v", tc.url, parseErr) |
| } |
| |
| gotPkg, gotModule, gotVersion, err := parseDetailsURLPath(u.Path) |
| if (err != nil) != tc.wantErr { |
| t.Fatalf("parseDetailsURLPath(%q) error = (%v); want error %t)", u, err, tc.wantErr) |
| } |
| if !tc.wantErr && (tc.wantModulePath != gotModule || tc.wantVersion != gotVersion || tc.wantFullPath != gotPkg) { |
| t.Fatalf("parseDetailsURLPath(%q): %q, %q, %q, %v; want = %q, %q, %q, want err %t", |
| u, gotPkg, gotModule, gotVersion, err, tc.wantFullPath, tc.wantModulePath, tc.wantVersion, tc.wantErr) |
| } |
| }) |
| } |
| } |
| |
| func TestValidatePathAndVersion(t *testing.T) { |
| tests := []struct { |
| path, version string |
| want int |
| }{ |
| {"import/path", "v1.2.3", http.StatusOK}, |
| {"import/path", "v1.2.bad", http.StatusBadRequest}, |
| } |
| |
| for _, test := range tests { |
| err := validatePathAndVersion(context.Background(), fakeDataSource{}, test.path, test.version) |
| var got int |
| if err == nil { |
| got = 200 |
| } else if serr, ok := err.(*serverError); ok { |
| got = serr.status |
| } else { |
| got = -1 |
| } |
| if got != test.want { |
| t.Errorf("validatePathAndVersion(ctx, ds, %q, %q): got code %d, want %d", test.path, test.version, got, test.want) |
| } |
| } |
| } |
| |
| type fakeDataSource struct { |
| internal.DataSource |
| } |
| |
| func TestNewContextFromExps(t *testing.T) { |
| for _, test := range []struct { |
| mods []string |
| want []string |
| }{ |
| { |
| mods: []string{"c", "a", "b"}, |
| want: []string{"a", "b", "c"}, |
| }, |
| { |
| mods: []string{"d", "a"}, |
| want: []string{"a", "b", "c", "d"}, |
| }, |
| { |
| mods: []string{"d", "!b", "!a", "c"}, |
| want: []string{"c", "d"}, |
| }, |
| } { |
| ctx := experiment.NewContext(context.Background(), "a", "b", "c") |
| ctx = newContextFromExps(ctx, test.mods) |
| got := experiment.FromContext(ctx).Active() |
| sort.Strings(got) |
| if !cmp.Equal(got, test.want) { |
| t.Errorf("mods=%v:\ngot %v\nwant %v", test.mods, got, test.want) |
| } |
| } |
| } |