internal/datasource: factor out GetUnit and GetUnitMeta

Move both methods into the shared datasource, and clean up the logic.

For golang/go#47780

Change-Id: I7ce0d29742652c63db2d2e55dedf6e378b838445
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/344950
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
diff --git a/internal/datasource/datasource.go b/internal/datasource/datasource.go
index c9a4888..25181ca 100644
--- a/internal/datasource/datasource.go
+++ b/internal/datasource/datasource.go
@@ -11,6 +11,7 @@
 	"context"
 	"errors"
 	"fmt"
+	"strings"
 	"time"
 
 	lru "github.com/hashicorp/golang-lru"
@@ -135,3 +136,68 @@
 	}
 	return nil, fmt.Errorf("%s@%s: %w", modulePath, version, derrors.NotFound)
 }
+
+// findModule finds the module with longest module path containing the given
+// package path. It returns an error if no module is found.
+func (ds *dataSource) findModule(ctx context.Context, pkgPath, modulePath, version string) (_ *internal.Module, err error) {
+	defer derrors.Wrap(&err, "findModule(%q, %q, %q)", pkgPath, modulePath, version)
+
+	if modulePath != internal.UnknownModulePath {
+		return ds.getModule(ctx, modulePath, version)
+	}
+	pkgPath = strings.TrimLeft(pkgPath, "/")
+	for _, modulePath := range internal.CandidateModulePaths(pkgPath) {
+		m, err := ds.getModule(ctx, modulePath, version)
+		if err == nil {
+			return m, nil
+		}
+		if !errors.Is(err, derrors.NotFound) {
+			return nil, err
+		}
+	}
+	return nil, fmt.Errorf("could not find module for import path %s: %w", pkgPath, derrors.NotFound)
+}
+
+// GetUnitMeta returns information about a path.
+func (ds *dataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *internal.UnitMeta, err error) {
+	defer derrors.Wrap(&err, "GetUnitMeta(%q, %q, %q)", path, requestedModulePath, requestedVersion)
+
+	module, err := ds.findModule(ctx, path, requestedModulePath, requestedVersion)
+	if err != nil {
+		return nil, err
+	}
+	um := &internal.UnitMeta{
+		Path:       path,
+		ModuleInfo: module.ModuleInfo,
+	}
+	if u := findUnit(module, path); u != nil {
+		um.Name = u.Name
+		um.IsRedistributable = u.IsRedistributable
+	}
+	return um, nil
+}
+
+// GetUnit returns information about a unit. Both the module path and package
+// path must be known.
+func (ds *dataSource) GetUnit(ctx context.Context, um *internal.UnitMeta, fields internal.FieldSet, bc internal.BuildContext) (_ *internal.Unit, err error) {
+	defer derrors.Wrap(&err, "GetUnit(%q, %q)", um.Path, um.ModulePath)
+
+	m, err := ds.getModule(ctx, um.ModulePath, um.Version)
+	if err != nil {
+		return nil, err
+	}
+	if u := findUnit(m, um.Path); u != nil {
+		return u, nil
+	}
+	return nil, fmt.Errorf("import path %s not found in module %s: %w", um.Path, um.ModulePath, derrors.NotFound)
+}
+
+// findUnit returns the unit with the given path in m, or nil if none.
+func findUnit(m *internal.Module, path string) *internal.Unit {
+	for _, u := range m.Units {
+		if u.Path == path {
+			return u
+		}
+	}
+	return nil
+}
diff --git a/internal/datasource/local.go b/internal/datasource/local.go
index 1b0a40e..2315d33 100644
--- a/internal/datasource/local.go
+++ b/internal/datasource/local.go
@@ -6,11 +6,9 @@
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
-	"strings"
 
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/derrors"
@@ -61,66 +59,14 @@
 	return ""
 }
 
+func (ds *LocalDataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *internal.UnitMeta, err error) {
+	return ds.ds.GetUnitMeta(ctx, path, requestedModulePath, requestedVersion)
+}
+
 // GetUnit returns information about a unit. Both the module path and package
 // path must be known.
 func (ds *LocalDataSource) GetUnit(ctx context.Context, pathInfo *internal.UnitMeta, fields internal.FieldSet, bc internal.BuildContext) (_ *internal.Unit, err error) {
-	defer derrors.Wrap(&err, "GetUnit(%q, %q)", pathInfo.Path, pathInfo.ModulePath)
-
-	module, err := ds.ds.getModule(ctx, pathInfo.ModulePath, pathInfo.Version)
-	if err != nil {
-		return nil, err
-	}
-	for _, unit := range module.Units {
-		if unit.Path == pathInfo.Path {
-			return unit, nil
-		}
-	}
-
-	return nil, fmt.Errorf("import path %s not found in module %s: %w", pathInfo.Path, pathInfo.ModulePath, derrors.NotFound)
-}
-
-// GetUnitMeta returns information about a path.
-func (ds *LocalDataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *internal.UnitMeta, err error) {
-	defer derrors.Wrap(&err, "GetUnitMeta(%q, %q, %q)", path, requestedModulePath, requestedVersion)
-
-	module, err := ds.findModule(ctx, path, requestedModulePath, requestedVersion)
-	if err != nil {
-		return nil, err
-	}
-	um := &internal.UnitMeta{
-		Path:       path,
-		ModuleInfo: module.ModuleInfo,
-	}
-
-	for _, u := range module.Units {
-		if u.Path == path {
-			um.Name = u.Name
-			um.IsRedistributable = u.IsRedistributable
-		}
-	}
-
-	return um, nil
-}
-
-// findModule finds the module with longest module path containing the given
-// package path. It returns an error if no module is found.
-func (ds *LocalDataSource) findModule(ctx context.Context, pkgPath, modulePath, version string) (_ *internal.Module, err error) {
-	defer derrors.Wrap(&err, "findModule(%q, %q, %q)", pkgPath, modulePath, version)
-
-	if modulePath != internal.UnknownModulePath {
-		return ds.ds.getModule(ctx, modulePath, version)
-	}
-	pkgPath = strings.TrimLeft(pkgPath, "/")
-	for _, modulePath := range internal.CandidateModulePaths(pkgPath) {
-		m, err := ds.ds.getModule(ctx, modulePath, version)
-		if err == nil {
-			return m, nil
-		}
-		if !errors.Is(err, derrors.NotFound) {
-			return nil, err
-		}
-	}
-	return nil, fmt.Errorf("could not find module for import path %s: %w", pkgPath, derrors.NotFound)
+	return ds.ds.GetUnit(ctx, pathInfo, fields, bc)
 }
 
 // GetLatestInfo is not implemented.
diff --git a/internal/datasource/proxy.go b/internal/datasource/proxy.go
index 5e34ccf..f3d08dc 100644
--- a/internal/datasource/proxy.go
+++ b/internal/datasource/proxy.go
@@ -52,41 +52,6 @@
 	ds *dataSource
 }
 
-// findModule finds the longest module path containing the given package path,
-// using the given finder func and iteratively testing parent directories of
-// the import path. It performs no testing as to whether the specified module
-// version that was found actually contains a package corresponding to pkgPath.
-func (ds *ProxyDataSource) findModule(ctx context.Context, pkgPath string, version string) (_ string, _ *proxy.VersionInfo, err error) {
-	defer derrors.Wrap(&err, "findModule(%q, ...)", pkgPath)
-	pkgPath = strings.TrimLeft(pkgPath, "/")
-	for _, modulePath := range internal.CandidateModulePaths(pkgPath) {
-		info, err := ds.ds.prox.Info(ctx, modulePath, version)
-		if errors.Is(err, derrors.NotFound) {
-			continue
-		}
-		if err != nil {
-			return "", nil, err
-		}
-		return modulePath, info, nil
-	}
-	return "", nil, fmt.Errorf("unable to find module: %w", derrors.NotFound)
-}
-
-// getUnit returns information about a unit.
-func (ds *ProxyDataSource) getUnit(ctx context.Context, fullPath, modulePath, version string, _ internal.BuildContext) (_ *internal.Unit, err error) {
-	var m *internal.Module
-	m, err = ds.ds.getModule(ctx, modulePath, version)
-	if err != nil {
-		return nil, err
-	}
-	for _, d := range m.Units {
-		if d.Path == fullPath {
-			return d, nil
-		}
-	}
-	return nil, fmt.Errorf("%q missing from module %s: %w", fullPath, m.ModulePath, derrors.NotFound)
-}
-
 // GetLatestInfo returns latest information for unitPath and modulePath.
 func (ds *ProxyDataSource) GetLatestInfo(ctx context.Context, unitPath, modulePath string, latestUnitMeta *internal.UnitMeta) (latest internal.LatestInfo, err error) {
 	defer derrors.Wrap(&err, "GetLatestInfo(ctx, %q, %q)", unitPath, modulePath)
diff --git a/internal/datasource/proxy_details.go b/internal/datasource/proxy_details.go
index d528562..3d01727 100644
--- a/internal/datasource/proxy_details.go
+++ b/internal/datasource/proxy_details.go
@@ -9,13 +9,12 @@
 
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/derrors"
-	"golang.org/x/pkgsite/internal/proxy"
 )
 
 // GetUnit returns information about a directory at a path.
 func (ds *ProxyDataSource) GetUnit(ctx context.Context, um *internal.UnitMeta, field internal.FieldSet, bc internal.BuildContext) (_ *internal.Unit, err error) {
 	defer derrors.Wrap(&err, "GetUnit(%q, %q, %q)", um.Path, um.ModulePath, um.Version)
-	return ds.getUnit(ctx, um.Path, um.ModulePath, um.Version, bc)
+	return ds.ds.GetUnit(ctx, um, field, bc)
 }
 
 // GetModuleInfo returns the ModuleInfo as fetched from the proxy for module
@@ -29,38 +28,8 @@
 	return &m.ModuleInfo, nil
 }
 
-// GetUnitMeta returns information about the given path.
-func (ds *ProxyDataSource) GetUnitMeta(ctx context.Context, path, inModulePath, inVersion string) (_ *internal.UnitMeta, err error) {
-	defer derrors.Wrap(&err, "GetUnitMeta(%q, %q, %q)", path, inModulePath, inVersion)
-
-	var info *proxy.VersionInfo
-	if inModulePath == internal.UnknownModulePath {
-		inModulePath, info, err = ds.findModule(ctx, path, inVersion)
-		if err != nil {
-			return nil, err
-		}
-		inVersion = info.Version
-	}
-	m, err := ds.ds.getModule(ctx, inModulePath, inVersion)
-	if err != nil {
-		return nil, err
-	}
-	um := &internal.UnitMeta{
-		Path: path,
-		ModuleInfo: internal.ModuleInfo{
-			ModulePath:        inModulePath,
-			Version:           inVersion,
-			IsRedistributable: m.IsRedistributable,
-		},
-	}
-	for _, d := range m.Units {
-		if d.Path == path {
-			um.Name = d.Name
-			um.IsRedistributable = d.IsRedistributable
-			break
-		}
-	}
-	return um, nil
+func (ds *ProxyDataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *internal.UnitMeta, err error) {
+	return ds.ds.GetUnitMeta(ctx, path, requestedModulePath, requestedVersion)
 }
 
 // GetExperiments is unimplemented.
diff --git a/internal/datasource/proxy_test.go b/internal/datasource/proxy_test.go
index fcf7a6d..4fcd767 100644
--- a/internal/datasource/proxy_test.go
+++ b/internal/datasource/proxy_test.go
@@ -128,6 +128,14 @@
 	ctx, ds, teardown := setup(t, false)
 	defer teardown()
 
+	singleModInfo := internal.ModuleInfo{
+		ModulePath:        "example.com/single",
+		Version:           "v1.0.0",
+		IsRedistributable: true,
+		CommitTime:        proxytest.CommitTime,
+		HasGoMod:          true,
+	}
+
 	for _, test := range []struct {
 		path, modulePath, version string
 		want                      *internal.UnitMeta
@@ -137,11 +145,7 @@
 			modulePath: "example.com/single",
 			version:    "v1.0.0",
 			want: &internal.UnitMeta{
-				ModuleInfo: internal.ModuleInfo{
-					ModulePath:        "example.com/single",
-					Version:           "v1.0.0",
-					IsRedistributable: true,
-				},
+				ModuleInfo:        singleModInfo,
 				IsRedistributable: true,
 			},
 		},
@@ -150,11 +154,7 @@
 			modulePath: "example.com/single",
 			version:    "v1.0.0",
 			want: &internal.UnitMeta{
-				ModuleInfo: internal.ModuleInfo{
-					ModulePath:        "example.com/single",
-					Version:           "v1.0.0",
-					IsRedistributable: true,
-				},
+				ModuleInfo:        singleModInfo,
 				Name:              "pkg",
 				IsRedistributable: true,
 			},
@@ -164,11 +164,7 @@
 			modulePath: internal.UnknownModulePath,
 			version:    "v1.0.0",
 			want: &internal.UnitMeta{
-				ModuleInfo: internal.ModuleInfo{
-					ModulePath:        "example.com/single",
-					Version:           "v1.0.0",
-					IsRedistributable: true,
-				},
+				ModuleInfo:        singleModInfo,
 				Name:              "pkg",
 				IsRedistributable: true,
 			},
@@ -182,6 +178,8 @@
 					ModulePath:        "example.com/basic",
 					Version:           "v1.1.0",
 					IsRedistributable: true,
+					CommitTime:        proxytest.CommitTime,
+					HasGoMod:          true,
 				},
 				Name:              "basic",
 				IsRedistributable: true,
@@ -194,7 +192,7 @@
 				t.Fatal(err)
 			}
 			test.want.Path = test.path
-			if diff := cmp.Diff(got, test.want); diff != "" {
+			if diff := cmp.Diff(test.want, got, cmpopts.IgnoreFields(internal.ModuleInfo{}, "SourceInfo")); diff != "" {
 				t.Errorf("mismatch (-want +got):\n%s", diff)
 			}
 		})
diff --git a/internal/proxy/proxytest/server.go b/internal/proxy/proxytest/server.go
index 7b020fc..86fcbb5 100644
--- a/internal/proxy/proxytest/server.go
+++ b/internal/proxy/proxytest/server.go
@@ -167,7 +167,8 @@
 	return s.zipRequests
 }
 
-const versionTime = "2019-01-30T00:00:00Z"
+// CommitTime is the time returned by all calls to the .info endpoint.
+var CommitTime = time.Date(2019, time.January, 30, 0, 0, 0, 0, time.UTC)
 
 func cleanModule(m *Module) *Module {
 	if m.Version == "" {
@@ -188,5 +189,6 @@
 }
 
 func defaultInfo(resolvedVersion string) *strings.Reader {
-	return strings.NewReader(fmt.Sprintf("{\n\t\"Version\": %q,\n\t\"Time\": %q\n}", resolvedVersion, versionTime))
+	return strings.NewReader(fmt.Sprintf("{\n\t\"Version\": %q,\n\t\"Time\": %q\n}",
+		resolvedVersion, CommitTime.Format(time.RFC3339)))
 }