internal/frontend/versions: remove test dependence on postgres db

This CL implements GetVersionsForPath on FakeDataSource (and removes
the error return from GetSymbolHistory) so that the FakeDataSource can
be used to replace the postgres db in TestFetchPackageVersionsDetails,
and remove the postgresdb from the test entirely.

For golang/go#61399

Change-Id: I84833a35029457d0a0a30f8d96b8b6238934e65c
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/521123
kokoro-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Michael Matloob <matloob@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/internal/frontend/versions/versions_test.go b/internal/frontend/versions/versions_test.go
index 35cd022..949d821 100644
--- a/internal/frontend/versions/versions_test.go
+++ b/internal/frontend/versions/versions_test.go
@@ -11,19 +11,13 @@
 	"github.com/google/go-cmp/cmp"
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/osv"
-	"golang.org/x/pkgsite/internal/postgres"
 	"golang.org/x/pkgsite/internal/stdlib"
+	"golang.org/x/pkgsite/internal/testing/fakedatasource"
 	"golang.org/x/pkgsite/internal/testing/sample"
 	"golang.org/x/pkgsite/internal/version"
 	"golang.org/x/pkgsite/internal/vuln"
 )
 
-var testDB *postgres.DB
-
-func TestMain(m *testing.M) {
-	postgres.RunDBTests("discovery_frontend_test", m, &testDB)
-}
-
 var (
 	modulePath1 = "test.com/module"
 	modulePath2 = "test.com/module/v2"
@@ -224,13 +218,13 @@
 	} {
 		t.Run(tc.name, func(t *testing.T) {
 			ctx := context.Background()
-			defer postgres.ResetTestDB(testDB, t)
+			fds := fakedatasource.New()
 
 			for _, v := range tc.modules {
-				postgres.MustInsertModule(ctx, t, testDB, v)
+				fds.MustInsertModule(ctx, v)
 			}
 
-			got, err := FetchVersionsDetails(ctx, testDB, &tc.pkg.UnitMeta, vc)
+			got, err := FetchVersionsDetails(ctx, fds, &tc.pkg.UnitMeta, vc)
 			if err != nil {
 				t.Fatalf("FetchVersionsDetails(ctx, db, %q, %q): %v", tc.pkg.Path, tc.pkg.ModulePath, err)
 			}
diff --git a/internal/testing/fakedatasource/fakedatasource.go b/internal/testing/fakedatasource/fakedatasource.go
index b1c6d0d..31ac0b4 100644
--- a/internal/testing/fakedatasource/fakedatasource.go
+++ b/internal/testing/fakedatasource/fakedatasource.go
@@ -365,7 +365,7 @@
 }
 
 func (ds *FakeDataSource) GetSymbolHistory(ctx context.Context, packagePath, modulePath string) (*internal.SymbolHistory, error) {
-	return nil, errNotImplemented
+	return &internal.SymbolHistory{}, nil
 }
 
 func (ds *FakeDataSource) GetVersionMap(ctx context.Context, modulePath, requestedVersion string) (*internal.VersionMap, error) {
@@ -376,8 +376,73 @@
 	return nil, errNotImplemented
 }
 
+// GetVersionsForPath returns a list of tagged versions sorted in
+// descending semver order if any exist. If none, it returns the 10 most
+// recent from a list of pseudo-versions sorted in descending semver order.
 func (ds *FakeDataSource) GetVersionsForPath(ctx context.Context, path string) ([]*internal.ModuleInfo, error) {
-	return nil, errNotImplemented
+	var infos []*internal.ModuleInfo
+
+	for _, m := range ds.modules {
+		if m.ModulePath == "std" {
+			for _, u := range m.Units {
+				if u.Path == path {
+					infos = append(infos, &m.ModuleInfo)
+					continue
+				}
+			}
+		}
+		prefix, _, _ := module.SplitPathVersion(m.ModulePath)
+		if !strings.HasPrefix(path, prefix) {
+			continue // different module
+		}
+		pathSuffix := trimSlashVersionPrefix(strings.TrimPrefix(path, prefix))
+		for _, u := range m.Units {
+			unitSuffix := trimSlashVersionPrefix(strings.TrimPrefix(u.Path, prefix))
+			if unitSuffix == pathSuffix {
+				infos = append(infos, &m.ModuleInfo)
+			}
+		}
+	}
+
+	// Only keep pseudoversions if we only have pseudoversions.
+	var nonPseudo []*internal.ModuleInfo
+	for _, info := range infos {
+		if !version.IsPseudo(info.Version) {
+			nonPseudo = append(nonPseudo, info)
+		}
+	}
+	if len(nonPseudo) > 0 {
+		infos = nonPseudo
+	}
+
+	sort.Slice(infos, func(i, j int) bool {
+		return version.ForSorting(infos[i].Version) > version.ForSorting(infos[j].Version)
+	})
+
+	if len(nonPseudo) == 0 && len(infos) > 10 {
+		infos = infos[:10]
+	}
+
+	return infos, nil
+}
+
+// TrimSlashVersionPrefix trims a /vN path component prefix if one is present in path,
+// and returns path unchanged otherwise.
+func trimSlashVersionPrefix(path string) string {
+	if !strings.HasPrefix(path, "/v") {
+		return path
+	}
+	trimSlash := path[len("/"):]
+	endOfPathComponent := strings.Index(trimSlash, "/")
+	if endOfPathComponent == -1 {
+		endOfPathComponent = len(trimSlash)
+	}
+	vComponent := trimSlash[:endOfPathComponent] // first component of the path
+	if m := semver.Major(vComponent); m == "" || m != vComponent {
+		return path
+	}
+	return trimSlash[endOfPathComponent:]
+
 }
 
 // InsertModule inserts m into the FakeDataSource. It is only implemented for