internal: add DataSource.GetLatestInfo

Add a single method to DataSource for getting all information
about latest versions.

Remove the old method.

Use the new method throughout.

Change-Id: Ie1c4894261021e91c9b889d00d98a2c7754017fd
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/279792
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/internal/datasource.go b/internal/datasource.go
index bd16f0a..0bdf1f3 100644
--- a/internal/datasource.go
+++ b/internal/datasource.go
@@ -11,14 +11,6 @@
 	// See the internal/postgres package for further documentation of these
 	// methods, particularly as they pertain to the main postgres implementation.
 
-	// GetLatestMajorVersion returns the latest module path and the full package path
-	// of the latest version found, given the fullPath and the modulePath.
-	// For example, in the module path "github.com/casbin/casbin", there
-	// is another module path with a greater major version "github.com/casbin/casbin/v3".
-	// This function will return "github.com/casbin/casbin/v3" or the input module path
-	// if no later module path was found. It also returns the full package path at the
-	// latest module version if it exists. If not, it returns the module path.
-	GetLatestMajorVersion(ctx context.Context, fullPath, modulePath string) (_ string, _ string, err error)
 	// GetNestedModules returns the latest major version of all nested modules
 	// given a modulePath path prefix.
 	GetNestedModules(ctx context.Context, modulePath string) ([]*ModuleInfo, error)
@@ -29,4 +21,37 @@
 	GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *UnitMeta, err error)
 	// GetModuleReadme gets the readme for the module.
 	GetModuleReadme(ctx context.Context, modulePath, resolvedVersion string) (*Readme, error)
+
+	// GetLatestInfo gets information about the latest versions of a unit and module.
+	// See LatestInfo for documentation.
+	GetLatestInfo(ctx context.Context, unitPath, modulePath string) (LatestInfo, error)
+}
+
+// LatestInfo holds information about the latest versions and paths.
+// The information is relative to a unit in a module.
+type LatestInfo struct {
+	// MinorVersion is the latest minor version for the unit, regardless of
+	// module.
+	MinorVersion string
+
+	// MinorModulePath is the module path for MinorVersion.
+	MinorModulePath string
+
+	// UnitExistsAtMinor is whether the unit exists at the latest minor version
+	// of the module
+	UnitExistsAtMinor bool
+
+	// MajorModulePath is the path of the latest module path in the series.
+	// For example, in the module path "github.com/casbin/casbin", there
+	// is another module path with a greater major version
+	// "github.com/casbin/casbin/v3". This field will be
+	// "github.com/casbin/casbin/v3" or the input module path if no later module
+	// path was found.
+	MajorModulePath string
+
+	// MajorUnitPath is the path of the unit in the latest major version of the
+	// module, if it exists. For example, if the module is M, the unit is M/U,
+	// and the latest major version is 3, then is field is "M/v3/U". If the module version
+	// at MajorModulePath does not contain this unit, then it is the module path."
+	MajorUnitPath string
 }
diff --git a/internal/frontend/latest_version.go b/internal/frontend/latest_version.go
index 5dff358..b7ac118 100644
--- a/internal/frontend/latest_version.go
+++ b/internal/frontend/latest_version.go
@@ -6,13 +6,9 @@
 
 import (
 	"context"
-	"errors"
-	"sync"
 
 	"golang.org/x/pkgsite/internal"
-	"golang.org/x/pkgsite/internal/derrors"
 	"golang.org/x/pkgsite/internal/log"
-	"golang.org/x/pkgsite/internal/middleware"
 )
 
 // GetLatestInfo returns various pieces of information about the latest
@@ -22,44 +18,16 @@
 //    fullPath and the modulePath.
 // It returns empty strings on error.
 // It is intended to be used as an argument to middleware.LatestVersions.
-func (s *Server) GetLatestInfo(ctx context.Context, unitPath, modulePath string) (latest middleware.LatestInfo) {
+func (s *Server) GetLatestInfo(ctx context.Context, unitPath, modulePath string) internal.LatestInfo {
 	// It is okay to use a different DataSource (DB connection) than the rest of the
 	// request, because this makes self-contained calls on the DB.
 	ds := s.getDataSource(ctx)
 
-	var wg sync.WaitGroup
-
-	wg.Add(1)
-	go func() {
-		defer wg.Done()
-		var err error
-		latest.MinorVersion, err = latestMinorVersion(ctx, ds, unitPath, internal.UnknownModulePath)
-		if err != nil {
-			log.Errorf(ctx, "latestMinorVersion: %v", err)
-		}
-	}()
-
-	wg.Add(1)
-	go func() {
-		defer wg.Done()
-		var err error
-		latest.MajorModulePath, latest.MajorUnitPath, err = ds.GetLatestMajorVersion(ctx, unitPath, modulePath)
-		if err != nil && !errors.Is(err, derrors.NotFound) {
-			log.Errorf(ctx, "GetLatestMajorVersion: %v", err)
-		}
-	}()
-
-	wg.Wait()
-	return latest
-}
-
-// TODO(https://github.com/golang/go/issues/40107): this is currently tested in server_test.go, but
-// we should add tests for this function.
-func latestMinorVersion(ctx context.Context, ds internal.DataSource, unitPath, modulePath string) (_ string, err error) {
-	defer derrors.Wrap(&err, "latestMinorVersion(ctx, %q, %q)", unitPath, modulePath)
-	um, err := ds.GetUnitMeta(ctx, unitPath, modulePath, internal.LatestVersion)
+	latest, err := ds.GetLatestInfo(ctx, unitPath, modulePath)
 	if err != nil {
-		return "", err
+		log.Errorf(ctx, "Server.GetLatestInfo: %v", err)
+	} else {
+		latest.MinorVersion = linkVersion(latest.MinorVersion, latest.MinorModulePath)
 	}
-	return linkVersion(um.Version, um.ModulePath), nil
+	return latest
 }
diff --git a/internal/localdatasource/datasource.go b/internal/localdatasource/datasource.go
index 20a8ffd..83e029a 100644
--- a/internal/localdatasource/datasource.go
+++ b/internal/localdatasource/datasource.go
@@ -170,12 +170,9 @@
 	return "", fmt.Errorf("%s not loaded: %w", pkgPath, derrors.NotFound)
 }
 
-// GetLatestMajorVersion returns the latest major version and the full package path
-// of any major version found given the seriesPath and the v1Path.
-// When fetching local modules, version is not accounted for, so an empty
-// string is returned.
-func (ds *DataSource) GetLatestMajorVersion(ctx context.Context, seriesPath string, v1Path string) (string, string, error) {
-	return "", "", nil
+// GetLatestInfo is not implemented.
+func (ds *DataSource) GetLatestInfo(ctx context.Context, unitPath, modulePath string) (internal.LatestInfo, error) {
+	return internal.LatestInfo{}, nil
 }
 
 // GetNestedModules is not implemented.
diff --git a/internal/middleware/latestversion.go b/internal/middleware/latestversion.go
index d7f2e0d..5960584 100644
--- a/internal/middleware/latestversion.go
+++ b/internal/middleware/latestversion.go
@@ -12,6 +12,7 @@
 	"strings"
 
 	"golang.org/x/mod/module"
+	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/log"
 )
 
@@ -26,14 +27,7 @@
 // latestInfoRegexp extracts values needed to determine the latest-version badge from a page's HTML.
 var latestInfoRegexp = regexp.MustCompile(`data-version="([^"]*)" data-mpath="([^"]*)" data-ppath="([^"]*)" data-pagetype="([^"]*)"`)
 
-// LatestInfo holds information about the latest versions and paths of a unit.
-type LatestInfo struct {
-	MinorVersion    string // latest minor version for unit path, regardless of module
-	MajorModulePath string // path of latest version of module
-	MajorUnitPath   string // path of unit in latest version of module
-}
-
-type latestFunc func(ctx context.Context, unitPath, modulePath string) LatestInfo
+type latestFunc func(ctx context.Context, unitPath, modulePath string) internal.LatestInfo
 
 // LatestVersions replaces the HTML placeholder values for the badge and banner
 // that displays whether the version of the package or module being served is
diff --git a/internal/middleware/latestversion_test.go b/internal/middleware/latestversion_test.go
index 7639287..977eb89 100644
--- a/internal/middleware/latestversion_test.go
+++ b/internal/middleware/latestversion_test.go
@@ -11,6 +11,8 @@
 	"net/http"
 	"net/http/httptest"
 	"testing"
+
+	"golang.org/x/pkgsite/internal"
 )
 
 func TestLatestMinorVersion(t *testing.T) {
@@ -154,8 +156,13 @@
 }
 
 func constLatestFunc(minorVersion, majorModPath, majorPackagePath string) latestFunc {
-	return func(context.Context, string, string) LatestInfo {
-		return LatestInfo{minorVersion, majorModPath, majorPackagePath}
+	return func(context.Context, string, string) internal.LatestInfo {
+		return internal.LatestInfo{
+			MinorVersion:    minorVersion,
+			MinorModulePath: "",
+			MajorModulePath: majorModPath,
+			MajorUnitPath:   majorPackagePath,
+		}
 	}
 }
 
diff --git a/internal/postgres/version.go b/internal/postgres/version.go
index 1a9c4c4..d1e100a 100644
--- a/internal/postgres/version.go
+++ b/internal/postgres/version.go
@@ -14,6 +14,7 @@
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/derrors"
 	"golang.org/x/pkgsite/internal/version"
+	"golang.org/x/sync/errgroup"
 )
 
 // GetVersionsForPath returns a list of tagged versions sorted in
@@ -102,15 +103,46 @@
 	return strings.Join(vs, ", ")
 }
 
-// GetLatestMajorVersion returns the latest module path and the full package path
+// GetLatestInfo returns the latest information about the unit in the module.
+// See internal.LatestInfo for documentation about the returned values.
+func (db *DB) GetLatestInfo(ctx context.Context, unitPath, modulePath string) (latest internal.LatestInfo, err error) {
+	defer derrors.Wrap(&err, "DB.GetLatestInfo(ctx, %q, %q)", unitPath, modulePath)
+
+	group, gctx := errgroup.WithContext(ctx)
+
+	group.Go(func() error {
+		um, err := db.GetUnitMeta(gctx, unitPath, internal.UnknownModulePath, internal.LatestVersion)
+		if err != nil {
+			return err
+		}
+		latest.MinorVersion = um.Version
+		latest.MinorModulePath = um.ModulePath
+		return nil
+	})
+	group.Go(func() (err error) {
+		latest.MajorModulePath, latest.MajorUnitPath, err = db.getLatestMajorVersion(gctx, unitPath, modulePath)
+		return err
+	})
+	group.Go(func() (err error) {
+		latest.UnitExistsAtMinor, err = db.getLatestMinorModuleVersionInfo(gctx, unitPath, modulePath)
+		return err
+	})
+
+	if err := group.Wait(); err != nil {
+		return internal.LatestInfo{}, err
+	}
+	return latest, nil
+}
+
+// getLatestMajorVersion returns the latest module path and the full package path
 // of the latest version found, given the fullPath and the modulePath.
 // For example, in the module path "github.com/casbin/casbin", there
 // is another module path with a greater major version "github.com/casbin/casbin/v3".
 // This function will return "github.com/casbin/casbin/v3" or the input module path
 // if no later module path was found. It also returns the full package path at the
 // latest module version if it exists. If not, it returns the module path.
-func (db *DB) GetLatestMajorVersion(ctx context.Context, fullPath, modulePath string) (_ string, _ string, err error) {
-	defer derrors.Wrap(&err, "DB.GetLatestMajorVersion(ctx, %q, %q)", fullPath, modulePath)
+func (db *DB) getLatestMajorVersion(ctx context.Context, fullPath, modulePath string) (_ string, _ string, err error) {
+	defer derrors.Wrap(&err, "DB.getLatestMajorVersion(ctx, %q, %q)", fullPath, modulePath)
 
 	var (
 		modID   int
@@ -143,24 +175,23 @@
 	}
 }
 
-// GetLatestMinorModuleVersion returns the latest minor version of modulePath,
-// and whether unitPath exists at that version.
-func (db *DB) GetLatestMinorModuleVersion(ctx context.Context, unitPath, modulePath string) (version string, unitExists bool, err error) {
-	defer derrors.Wrap(&err, "DB.GetLatestMinorVersion(ctx, %q, %q)", unitPath, modulePath)
+// getLatestMinorModuleVersion reports whether unitPath exists at the latest version of modulePath.
+func (db *DB) getLatestMinorModuleVersionInfo(ctx context.Context, unitPath, modulePath string) (unitExists bool, err error) {
+	defer derrors.Wrap(&err, "DB.getLatestMinorVersion(ctx, %q, %q)", unitPath, modulePath)
 
 	// Find the latest version of the module path.
 	var modID int
-	q, args, err := orderByLatest(squirrel.Select("m.version", "m.id").
+	q, args, err := orderByLatest(squirrel.Select("m.id").
 		From("modules m").
 		Where(squirrel.Eq{"m.module_path": modulePath})).
 		Limit(1).
 		ToSql()
 	if err != nil {
-		return "", false, err
+		return false, err
 	}
 	row := db.db.QueryRow(ctx, q, args...)
-	if err := row.Scan(&version, &modID); err != nil {
-		return "", false, err
+	if err := row.Scan(&modID); err != nil {
+		return false, err
 	}
 
 	// See if the unit path exists at that version.
@@ -168,10 +199,10 @@
 	err = db.db.QueryRow(ctx, `SELECT 1 FROM units WHERE path = $1 AND module_id = $2`, unitPath, modID).Scan(&x)
 	switch err {
 	case nil:
-		return version, true, nil
+		return true, nil
 	case sql.ErrNoRows:
-		return version, false, nil
+		return false, nil
 	default:
-		return "", false, err
+		return false, err
 	}
 }
diff --git a/internal/postgres/version_test.go b/internal/postgres/version_test.go
index d4713a5..b3adc90 100644
--- a/internal/postgres/version_test.go
+++ b/internal/postgres/version_test.go
@@ -6,8 +6,6 @@
 
 import (
 	"context"
-	"database/sql"
-	"errors"
 	"fmt"
 	"testing"
 
@@ -231,16 +229,17 @@
 	}
 }
 
-func TestGetLatestMajorVersion(t *testing.T) {
+func TestGetLatestInfo(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
 	defer cancel()
-
 	defer ResetTestDB(testDB, t)
+
 	for _, m := range []*internal.Module{
-		sample.Module("foo.com/bar", "v1.1.1", "baz", "faz"),
-		sample.Module("foo.com/bar/v2", "v2.0.5", "baz", "faz"),
-		sample.Module("foo.com/bar/v3", "v3.0.1", "baz"),
-		sample.Module("bar.com/foo", sample.VersionString, sample.Suffix),
+		sample.Module("a.com/M", "v1.1.1", "all", "most", "some", "one", "D/other"),
+		sample.Module("a.com/M", "v1.2.0", "all", "most"),
+		sample.Module("a.com/M/v2", "v2.0.5", "all", "most"),
+		sample.Module("a.com/M/v3", "v3.0.1", "all", "some"),
+		sample.Module("a.com/M/D", "v1.3.0", "other"),
 	} {
 		if err := testDB.InsertModule(ctx, m); err != nil {
 			t.Fatal(err)
@@ -248,93 +247,84 @@
 	}
 
 	for _, test := range []struct {
-		fullPath        string
-		modulePath      string
-		wantModulePath  string
-		wantPackagePath string
-		wantErr         error
+		unit string
+		want internal.LatestInfo
 	}{
 		{
-			fullPath:        "foo.com/bar",
-			modulePath:      "foo.com/bar",
-			wantModulePath:  "foo.com/bar/v3",
-			wantPackagePath: "foo.com/bar/v3",
+			// A unit that is the module.
+			"a.com/M",
+			internal.LatestInfo{
+				MinorVersion:      "v1.2.0",
+				MinorModulePath:   "a.com/M",
+				UnitExistsAtMinor: true,
+				MajorModulePath:   "a.com/M/v3",
+				MajorUnitPath:     "a.com/M/v3",
+			},
 		},
 		{
-			fullPath:        "bar.com/foo",
-			modulePath:      "bar.com/foo",
-			wantModulePath:  "bar.com/foo",
-			wantPackagePath: "bar.com/foo",
+			// A unit that exists in all versions of the module.
+			"a.com/M/all",
+			internal.LatestInfo{
+				MinorVersion:      "v1.2.0",
+				MinorModulePath:   "a.com/M",
+				UnitExistsAtMinor: true,
+				MajorModulePath:   "a.com/M/v3",
+				MajorUnitPath:     "a.com/M/v3/all",
+			},
 		},
 		{
-			fullPath:   "boo.com/far",
-			modulePath: "boo.com/far",
-			wantErr:    sql.ErrNoRows,
+			// A unit that exists in most versions, but not the latest major.
+			"a.com/M/most",
+			internal.LatestInfo{
+				MinorVersion:      "v1.2.0",
+				MinorModulePath:   "a.com/M",
+				UnitExistsAtMinor: true,
+				MajorModulePath:   "a.com/M/v3",
+				MajorUnitPath:     "a.com/M/v3",
+			},
 		},
 		{
-			fullPath:        "foo.com/bar/baz",
-			modulePath:      "foo.com/bar",
-			wantModulePath:  "foo.com/bar/v3",
-			wantPackagePath: "foo.com/bar/v3/baz",
+			// A unit that does not exist at the latest minor version, but does at the latest major.
+			"a.com/M/some",
+			internal.LatestInfo{
+				MinorVersion:      "v1.1.1",
+				MinorModulePath:   "a.com/M",
+				UnitExistsAtMinor: false,
+				MajorModulePath:   "a.com/M/v3",
+				MajorUnitPath:     "a.com/M/v3/some",
+			},
 		},
 		{
-			fullPath:        "foo.com/bar/faz",
-			modulePath:      "foo.com/bar",
-			wantModulePath:  "foo.com/bar/v3",
-			wantPackagePath: "foo.com/bar/v3",
+			// A unit that does not exist at the latest minor or major versions.
+			"a.com/M/one",
+			internal.LatestInfo{
+				MinorVersion:      "v1.1.1",
+				MinorModulePath:   "a.com/M",
+				UnitExistsAtMinor: false,
+				MajorModulePath:   "a.com/M/v3",
+				MajorUnitPath:     "a.com/M/v3",
+			},
+		},
+		{
+			// A unit whose latest minor version is in a different module.
+			"a.com/M/D/other",
+			internal.LatestInfo{
+				MinorVersion:      "v1.3.0",
+				MinorModulePath:   "a.com/M/D",
+				UnitExistsAtMinor: false,
+				MajorModulePath:   "a.com/M/v3",
+				MajorUnitPath:     "a.com/M/v3",
+			},
 		},
 	} {
-		gotVersion, gotPath, err := testDB.GetLatestMajorVersion(ctx, test.fullPath, test.modulePath)
-		if err != nil {
-			if test.wantErr == nil {
-				t.Fatalf("got unexpected error %v", err)
+		t.Run(test.unit, func(t *testing.T) {
+			got, err := testDB.GetLatestInfo(ctx, test.unit, "a.com/M")
+			if err != nil {
+				t.Fatal(err)
 			}
-			if !errors.Is(err, test.wantErr) {
-				t.Errorf("got err = %v, want Is(%v)", err, test.wantErr)
+			if diff := cmp.Diff(test.want, got); diff != "" {
+				t.Errorf("mismatch (-want, +got):\n%s", diff)
 			}
-		}
-		if gotVersion != test.wantModulePath || gotPath != test.wantPackagePath {
-			t.Errorf("testDB.GetLatestMajorVersion(%v, %v) = (%v, %v), want = (%v, %v)", test.fullPath, test.modulePath, gotVersion, gotPath, test.wantModulePath, test.wantPackagePath)
-		}
-	}
-}
-
-func TestGetLatestMinorModuleVersion(t *testing.T) {
-	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
-	defer cancel()
-	defer ResetTestDB(testDB, t)
-
-	const (
-		modulePath    = "foo.com/M"
-		latestVersion = "v1.2.0"
-	)
-
-	for _, m := range []*internal.Module{
-		sample.Module(modulePath, "v1.1.0", "p1", "p2"),
-		sample.Module(modulePath, latestVersion, "p1"),
-		sample.Module(modulePath+"/v2", "v2.0.5", "p1", "p2"),
-	} {
-		if err := testDB.InsertModule(ctx, m); err != nil {
-			t.Fatal(err)
-		}
-	}
-
-	for _, test := range []struct {
-		unitSuffix  string
-		wantPresent bool
-	}{
-		{"p1", true},
-		{"p2", false},
-	} {
-		gotVersion, gotPresent, err := testDB.GetLatestMinorModuleVersion(ctx, modulePath+"/"+test.unitSuffix, modulePath)
-		if err != nil {
-			t.Fatal(err)
-		}
-		if gotVersion != latestVersion {
-			t.Errorf("%s: got version %q, want %q", test.unitSuffix, gotVersion, latestVersion)
-		}
-		if gotPresent != test.wantPresent {
-			t.Errorf("%s: got present %t, want %t", test.unitSuffix, gotPresent, test.wantPresent)
-		}
+		})
 	}
 }
diff --git a/internal/proxydatasource/datasource.go b/internal/proxydatasource/datasource.go
index 6e9a3ed..bf453b3 100644
--- a/internal/proxydatasource/datasource.go
+++ b/internal/proxydatasource/datasource.go
@@ -176,11 +176,31 @@
 	return nil, fmt.Errorf("%q missing from module %s: %w", fullPath, m.ModulePath, derrors.NotFound)
 }
 
-// GetLatestMajorVersion returns the latest module path and the full package path
+// GetLatestInfo returns latest information for unitPath and modulePath.
+func (ds *DataSource) GetLatestInfo(ctx context.Context, unitPath, modulePath string) (latest internal.LatestInfo, err error) {
+	defer derrors.Wrap(&err, "GetLatestInfo(ctx, %q, %q)", unitPath, modulePath)
+
+	um, err := ds.GetUnitMeta(ctx, unitPath, internal.UnknownModulePath, internal.LatestVersion)
+	if err != nil {
+		return latest, err
+	}
+	latest.MinorVersion = um.Version
+	latest.MinorModulePath = um.ModulePath
+
+	latest.MajorModulePath, latest.MajorUnitPath, err = ds.getLatestMajorVersion(ctx, unitPath, modulePath)
+	if err != nil {
+		return latest, err
+	}
+	// Do not try to discover whether the unit is in the latest minor version; assume it is.
+	latest.UnitExistsAtMinor = true
+	return latest, nil
+}
+
+// getLatestMajorVersion returns the latest module path and the full package path
 // of the latest version found in the proxy by iterating through vN versions.
 // This function does not attempt to find whether the full path exists
 // in the new major version.
-func (ds *DataSource) GetLatestMajorVersion(ctx context.Context, fullPath, modulePath string) (_ string, _ string, err error) {
+func (ds *DataSource) getLatestMajorVersion(ctx context.Context, fullPath, modulePath string) (_ string, _ string, err error) {
 	// We are checking if the full path is valid so that we can forward the error if not.
 	seriesPath := internal.SeriesPathForModule(modulePath)
 	info, err := ds.proxyClient.GetInfo(ctx, seriesPath, internal.LatestVersion)
diff --git a/internal/proxydatasource/datasource_test.go b/internal/proxydatasource/datasource_test.go
index 5fee0d8..fd4545b 100644
--- a/internal/proxydatasource/datasource_test.go
+++ b/internal/proxydatasource/datasource_test.go
@@ -197,24 +197,36 @@
 	}
 }
 
-func TestDataSource_GetLatestMajorVersion(t *testing.T) {
+func TestDataSource_GetLatestInfo(t *testing.T) {
 	t.Helper()
 	testModules := []*proxy.Module{
 		{
 			ModulePath: "foo.com/bar",
+			Version:    "v1.1.0",
+			Files: map[string]string{
+				"baz.go": "package bar",
+			},
 		},
 		{
 			ModulePath: "foo.com/bar/v2",
+			Version:    "v2.0.5",
 		},
 		{
 			ModulePath: "foo.com/bar/v3",
 		},
 		{
 			ModulePath: "bar.com/foo",
+			Version:    "v1.1.0",
+			Files: map[string]string{
+				"baz.go": "package foo",
+			},
 		},
 		{
 			ModulePath: "incompatible.com/bar",
 			Version:    "v2.1.1+incompatible",
+			Files: map[string]string{
+				"baz.go": "package bar",
+			},
 		},
 		{
 			ModulePath: "incompatible.com/bar/v3",
@@ -263,7 +275,7 @@
 			wantPackagePath: "incompatible.com/bar/v3",
 		},
 	} {
-		gotVersion, gotPath, err := ds.GetLatestMajorVersion(ctx, test.fullPath, test.modulePath)
+		gotLatest, err := ds.GetLatestInfo(ctx, test.fullPath, test.modulePath)
 		if err != nil {
 			if test.wantErr == nil {
 				t.Fatalf("got unexpected error %v", err)
@@ -272,8 +284,9 @@
 				t.Errorf("got err = %v, want Is(%v)", err, test.wantErr)
 			}
 		}
-		if gotVersion != test.wantModulePath || gotPath != test.wantPackagePath {
-			t.Errorf("ds.GetLatestMajorVersion(%v, %v) = (%v, %v), want = (%v, %v)", test.fullPath, test.modulePath, gotVersion, gotPath, test.wantModulePath, test.wantPackagePath)
+		if gotLatest.MajorModulePath != test.wantModulePath || gotLatest.MajorUnitPath != test.wantPackagePath {
+			t.Errorf("ds.GetLatestMajorVersion(%v, %v) = (%v, %v), want = (%v, %v)",
+				test.fullPath, test.modulePath, gotLatest.MajorModulePath, gotLatest.MajorUnitPath, test.wantModulePath, test.wantPackagePath)
 		}
 	}
 }