internal: delete experiment not-at-v1

Additionally, only redirect if the non-v1 path does not match the full
path, and is actually a non-v1 path.

Otherwise, this will redirect requests to <path>@<missing-version> to
<path>@latest, or <path>/v3 to <path>, instead of return a 404 that
allows the user to fetch the missing version.

Change-Id: I66946062d341477fdc19bfc2a0f18b848943807d
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/284580
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/experiment.go b/internal/experiment.go
index 1cb64a9..e45d0a2 100644
--- a/internal/experiment.go
+++ b/internal/experiment.go
@@ -8,7 +8,6 @@
 const (
 	ExperimentDirectoryTree        = "directory-tree"
 	ExperimentNotAtLatest          = "not-at-latest"
-	ExperimentNotAtV1              = "not-at-v1"
 	ExperimentRedirectedFromBanner = "redirected-from-banner"
 )
 
@@ -17,7 +16,6 @@
 var Experiments = map[string]string{
 	ExperimentDirectoryTree:        "Enable the directory tree layout on the unit page.",
 	ExperimentNotAtLatest:          "Enable the display of a 'not at latest' badge.",
-	ExperimentNotAtV1:              "Redirect requests to a path not at v1 to the highest major version of that path.",
 	ExperimentRedirectedFromBanner: "Display banner with path that request was redirected from.",
 }
 
diff --git a/internal/frontend/404.go b/internal/frontend/404.go
index 5fcf653..e3f3718 100644
--- a/internal/frontend/404.go
+++ b/internal/frontend/404.go
@@ -16,7 +16,6 @@
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/cookie"
 	"golang.org/x/pkgsite/internal/derrors"
-	"golang.org/x/pkgsite/internal/experiment"
 	"golang.org/x/pkgsite/internal/log"
 	"golang.org/x/pkgsite/internal/postgres"
 	"golang.org/x/pkgsite/internal/stdlib"
@@ -141,27 +140,29 @@
 		}, fullPath, requestedVersion)
 	}
 
-	if experiment.IsActive(ctx, internal.ExperimentNotAtV1) {
-		// Check if the unit path exists at a higher major version.
-		// For example, my.module might not exist, but my.module/v3 might.
-		// Similarly, my.module/foo might not exist, but my.module/v3/foo might.
-		// In either case, the user will be redirected to the highest major version
-		// of the path.
-		//
-		// Do not bother to look for a specific version if this case. If
-		// my.module/foo@v2.1.0 was requested, and my.module/foo/v2 exists, just
-		// return the latest version of my.module/foo/v2.
-		majPath, err := db.GetLatestMajorPathForV1Path(ctx, fullPath)
-		if err != nil && err != derrors.NotFound {
-			return nil, err
-		}
-		if majPath != "" {
-			return &fetchResult{
-				modulePath: majPath,
-				goModPath:  majPath,
-				status:     http.StatusFound,
-			}, nil
-		}
+	// Check if the unit path exists at a higher major version.
+	// For example, my.module might not exist, but my.module/v3 might.
+	// Similarly, my.module/foo might not exist, but my.module/v3/foo might.
+	// In either case, the user will be redirected to the highest major version
+	// of the path.
+	//
+	// Do not bother to look for a specific version if this case. If
+	// my.module/foo@v2.1.0 was requested, and my.module/foo/v2 exists, just
+	// return the latest version of my.module/foo/v2.
+	//
+	// Only redirect if the majPath returned is different from the fullPath, and
+	// the majPath is not at v1. We don't want to redirect my.module/foo/v3 to
+	// my.module/foo, or my.module/foo@v1.5.2 to my.module/foo@v1.0.0.
+	majPath, maj, err := db.GetLatestMajorPathForV1Path(ctx, fullPath)
+	if err != nil && err != derrors.NotFound {
+		return nil, err
+	}
+	if majPath != fullPath && maj != 1 && majPath != "" {
+		return &fetchResult{
+			modulePath: majPath,
+			goModPath:  majPath,
+			status:     http.StatusFound,
+		}, nil
 	}
 
 	// The full path does not exist in our database, but its module might.
diff --git a/internal/frontend/404_test.go b/internal/frontend/404_test.go
index aa30240..b95f2db 100644
--- a/internal/frontend/404_test.go
+++ b/internal/frontend/404_test.go
@@ -8,14 +8,12 @@
 	"context"
 	"errors"
 	"net/http"
-	"strings"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/derrors"
-	"golang.org/x/pkgsite/internal/experiment"
 	"golang.org/x/pkgsite/internal/postgres"
 	"golang.org/x/pkgsite/internal/testing/sample"
 )
@@ -124,15 +122,15 @@
 		}
 	}
 
-	checkPath := func(ctx context.Context, t *testing.T, testDB *postgres.DB, path, wantPath string, status int) {
-		got, err := previousFetchStatusAndResponse(ctx, testDB, path, internal.LatestVersion)
+	checkPath := func(ctx context.Context, t *testing.T, testDB *postgres.DB, path, version, wantPath string, wantStatus int) {
+		got, err := previousFetchStatusAndResponse(ctx, testDB, path, version)
 		if err != nil {
 			t.Fatal(err)
 		}
 		want := &fetchResult{
 			modulePath: wantPath,
 			goModPath:  wantPath,
-			status:     status,
+			status:     wantStatus,
 		}
 		if diff := cmp.Diff(want, got,
 			cmp.AllowUnexported(fetchResult{}),
@@ -142,18 +140,26 @@
 	}
 
 	for _, test := range []struct {
-		name, path, want string
+		name, path, version, wantPath string
+		wantStatus                    int
 	}{
-		{"module path not at v1", sample.ModulePath, sample.ModulePath + "/v4"},
-		{"import path not at v1", sample.ModulePath + "/foo", sample.ModulePath + "/v4/foo"},
+		{"module path not at v1", sample.ModulePath, internal.LatestVersion, sample.ModulePath + "/v4", http.StatusFound},
+		{"import path not at v1", sample.ModulePath + "/foo", internal.LatestVersion, sample.ModulePath + "/v4/foo", http.StatusFound},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			want := strings.ReplaceAll(test.want, "/v4", "")
-			checkPath(ctx, t, testDB, test.path, want, http.StatusNotFound)
+			checkPath(ctx, t, testDB, test.path, test.version, test.wantPath, test.wantStatus)
 		})
-		t.Run(test.name+"not-at-v1", func(t *testing.T) {
-			ctx := experiment.NewContext(ctx, internal.ExperimentNotAtV1)
-			checkPath(ctx, t, testDB, test.path, test.want, http.StatusFound)
+	}
+	for _, test := range []struct {
+		name, path, version string
+	}{
+		{"import path v1 missing version", sample.ModulePath + "/foo", "v1.5.2"},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			_, err := previousFetchStatusAndResponse(ctx, testDB, test.path, test.version)
+			if !errors.Is(err, derrors.NotFound) {
+				t.Fatal(err)
+			}
 		})
 	}
 }
diff --git a/internal/frontend/server_test.go b/internal/frontend/server_test.go
index 477e361..380a6ef 100644
--- a/internal/frontend/server_test.go
+++ b/internal/frontend/server_test.go
@@ -1220,8 +1220,7 @@
 		}
 	}
 
-	_, handler, _ := newTestServer(t, nil, internal.ExperimentNotAtV1)
-	_, handlerNoExp, _ := newTestServer(t, nil)
+	_, handler, _ := newTestServer(t, nil)
 
 	for _, test := range []struct {
 		name, path, flash string
@@ -1236,12 +1235,6 @@
 		t.Run(test.name, func(t *testing.T) {
 			w := httptest.NewRecorder()
 			handler.ServeHTTP(w, httptest.NewRequest("GET", test.path, nil))
-			if w.Code != test.wantCode {
-				t.Errorf("%q: got status code = %d, want %d", test.path, w.Code, test.wantCode)
-			}
-
-			w2 := httptest.NewRecorder()
-			handlerNoExp.ServeHTTP(w2, httptest.NewRequest("GET", test.path, nil))
 			if test.path == v1modpath || test.path == v1path {
 				test.wantCode = http.StatusNotFound
 			}
diff --git a/internal/postgres/path.go b/internal/postgres/path.go
index 225c8d3..5312926 100644
--- a/internal/postgres/path.go
+++ b/internal/postgres/path.go
@@ -17,7 +17,7 @@
 
 // GetLatestMajorPathForV1Path reports the latest unit path in the series for
 // the given v1path.
-func (db *DB) GetLatestMajorPathForV1Path(ctx context.Context, v1path string) (_ string, err error) {
+func (db *DB) GetLatestMajorPathForV1Path(ctx context.Context, v1path string) (_ string, _ int, err error) {
 	defer derrors.Wrap(&err, "DB.GetLatestPathForV1Path(ctx, %q)", v1path)
 	q := `
 		SELECT p.path, m.series_path
@@ -42,7 +42,7 @@
 		return nil
 	}, v1path)
 	if err != nil {
-		return "", err
+		return "", 0, err
 	}
 
 	var (
@@ -59,7 +59,7 @@
 		if v != "" {
 			i, err = strconv.Atoi(v)
 			if err != nil {
-				return "", fmt.Errorf("strconv.Atoi(%q): %v", v, err)
+				return "", 0, fmt.Errorf("strconv.Atoi(%q): %v", v, err)
 			}
 		}
 		if maj <= i {
@@ -67,5 +67,9 @@
 			majPath = p
 		}
 	}
-	return majPath, nil
+	if maj == 0 {
+		// Return 1 as the major version for all v0 or v1 majPaths.
+		maj = 1
+	}
+	return majPath, maj, nil
 }
diff --git a/internal/postgres/path_test.go b/internal/postgres/path_test.go
index bdadb27..cf7dbac 100644
--- a/internal/postgres/path_test.go
+++ b/internal/postgres/path_test.go
@@ -6,6 +6,8 @@
 
 import (
 	"context"
+	"strconv"
+	"strings"
 	"testing"
 
 	"golang.org/x/pkgsite/internal/testing/sample"
@@ -15,18 +17,27 @@
 	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
 	defer cancel()
 
-	checkLatest := func(t *testing.T, versions []string, v1path string, wantVersion string) {
+	checkLatest := func(t *testing.T, versions []string, v1path string, version, suffix string) {
 		t.Helper()
-		got, err := testDB.GetLatestMajorPathForV1Path(ctx, v1path)
+		gotPath, gotVer, err := testDB.GetLatestMajorPathForV1Path(ctx, v1path)
 		if err != nil {
 			t.Fatal(err)
 		}
 		want := sample.ModulePath
-		if wantVersion != "" {
-			want = want + "/" + wantVersion
+		if suffix != "" {
+			want = want + "/" + suffix
 		}
-		if got != want {
-			t.Errorf("GetLatestMajorPathForV1Path(%q) = %q, want %q", v1path, got, want)
+		var wantVer int
+		if version == "" {
+			wantVer = 1
+		} else {
+			wantVer, err = strconv.Atoi(strings.TrimPrefix(version, "v"))
+			if err != nil {
+				t.Fatal(err)
+			}
+		}
+		if gotPath != want || gotVer != wantVer {
+			t.Errorf("GetLatestMajorPathForV1Path(%q) = %q, %d, want %q, %d", v1path, gotPath, gotVer, want, wantVer)
 		}
 	}
 
@@ -71,14 +82,15 @@
 			}
 			t.Run("module", func(t *testing.T) {
 				v1path := sample.ModulePath
-				checkLatest(t, test.versions, v1path, test.want)
+				checkLatest(t, test.versions, v1path, test.want, test.want)
 			})
 			t.Run("package", func(t *testing.T) {
+				want := test.want
 				if test.want != "" {
-					test.want += "/"
+					want += "/"
 				}
 				v1path := sample.ModulePath + "/" + suffix
-				checkLatest(t, test.versions, v1path, test.want+suffix)
+				checkLatest(t, test.versions, v1path, test.want, want+suffix)
 			})
 		})
 	}