internal/postgres: move GetModuleLicenses and GetPackageLicenses

GetModuleLicenses and GetPackageLicenses and their helper functions are
moved to licenses.go, since details.go was getting long.

There are no code changes.

Change-Id: Iba282d24dad662da38093d66f995f38f2d085ef5
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/238022
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/postgres/details.go b/internal/postgres/details.go
index f9b7908..6558531 100644
--- a/internal/postgres/details.go
+++ b/internal/postgres/details.go
@@ -11,14 +11,12 @@
 	"errors"
 	"fmt"
 	"reflect"
-	"sort"
 	"strings"
 
 	"github.com/lib/pq"
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/database"
 	"golang.org/x/pkgsite/internal/derrors"
-	"golang.org/x/pkgsite/internal/licenses"
 	"golang.org/x/pkgsite/internal/version"
 )
 
@@ -280,147 +278,6 @@
 	return importedby, nil
 }
 
-// GetModuleLicenses returns all licenses associated with the given module path and
-// version. These are the top-level licenses in the module zip file.
-// It returns an InvalidArgument error if the module path or version is invalid.
-func (db *DB) GetModuleLicenses(ctx context.Context, modulePath, version string) (_ []*licenses.License, err error) {
-	defer derrors.Wrap(&err, "GetModuleLicenses(ctx, %q, %q)", modulePath, version)
-
-	if modulePath == "" || version == "" {
-		return nil, fmt.Errorf("neither modulePath nor version can be empty: %w", derrors.InvalidArgument)
-	}
-	query := `
-	SELECT
-		types, file_path, contents, coverage
-	FROM
-		licenses
-	WHERE
-		module_path = $1 AND version = $2 AND position('/' in file_path) = 0
-    `
-	rows, err := db.db.Query(ctx, query, modulePath, version)
-	if err != nil {
-		return nil, err
-	}
-	defer rows.Close()
-	return collectLicenses(rows)
-}
-
-// GetPackageLicenses returns all licenses associated with the given package path and
-// version.
-// It returns an InvalidArgument error if the module path or version is invalid.
-func (db *DB) GetPackageLicenses(ctx context.Context, pkgPath, modulePath, version string) (_ []*licenses.License, err error) {
-	defer derrors.Wrap(&err, "GetPackageLicenses(ctx, %q, %q, %q)", pkgPath, modulePath, version)
-
-	if pkgPath == "" || version == "" {
-		return nil, fmt.Errorf("neither pkgPath nor version can be empty: %w", derrors.InvalidArgument)
-	}
-	query := `
-		SELECT
-			l.types,
-			l.file_path,
-			l.contents,
-			l.coverage
-		FROM
-			licenses l
-		INNER JOIN (
-			SELECT DISTINCT ON (license_file_path)
-				module_path,
-				version,
-				unnest(license_paths) AS license_file_path
-			FROM
-				packages
-			WHERE
-				path = $1
-				AND module_path = $2
-				AND version = $3
-		) p
-		ON
-			p.module_path = l.module_path
-			AND p.version = l.version
-			AND p.license_file_path = l.file_path;`
-
-	rows, err := db.db.Query(ctx, query, pkgPath, modulePath, version)
-	if err != nil {
-		return nil, err
-	}
-	defer rows.Close()
-	return collectLicenses(rows)
-}
-
-// collectLicenses converts the sql rows to a list of licenses. The columns
-// must be types, file_path and contents, in that order.
-func collectLicenses(rows *sql.Rows) ([]*licenses.License, error) {
-	mustHaveColumns(rows, "types", "file_path", "contents", "coverage")
-	var lics []*licenses.License
-	for rows.Next() {
-		var (
-			lic          = &licenses.License{Metadata: &licenses.Metadata{}}
-			licenseTypes []string
-		)
-		if err := rows.Scan(pq.Array(&licenseTypes), &lic.FilePath, &lic.Contents, jsonbScanner{&lic.Coverage}); err != nil {
-			return nil, fmt.Errorf("row.Scan(): %v", err)
-		}
-		lic.Types = licenseTypes
-		lics = append(lics, lic)
-	}
-	sort.Slice(lics, func(i, j int) bool {
-		return compareLicenses(lics[i].Metadata, lics[j].Metadata)
-	})
-	if err := rows.Err(); err != nil {
-		return nil, err
-	}
-	return lics, nil
-}
-
-// mustHaveColumns panics if the columns of rows does not match wantColumns.
-func mustHaveColumns(rows *sql.Rows, wantColumns ...string) {
-	gotColumns, err := rows.Columns()
-	if err != nil {
-		panic(err)
-	}
-	if !reflect.DeepEqual(gotColumns, wantColumns) {
-		panic(fmt.Sprintf("got columns %v, want $%v", gotColumns, wantColumns))
-	}
-}
-
-// zipLicenseMetadata constructs licenses.Metadata from the given license types
-// and paths, by zipping and then sorting.
-func zipLicenseMetadata(licenseTypes []string, licensePaths []string) (_ []*licenses.Metadata, err error) {
-	defer derrors.Wrap(&err, "zipLicenseMetadata(%v, %v)", licenseTypes, licensePaths)
-
-	if len(licenseTypes) != len(licensePaths) {
-		return nil, fmt.Errorf("BUG: got %d license types and %d license paths", len(licenseTypes), len(licensePaths))
-	}
-	byPath := make(map[string]*licenses.Metadata)
-	var mds []*licenses.Metadata
-	for i, p := range licensePaths {
-		md, ok := byPath[p]
-		if !ok {
-			md = &licenses.Metadata{FilePath: p}
-			mds = append(mds, md)
-		}
-		// By convention, we insert a license path with empty corresponding license
-		// type if we are unable to detect *any* licenses in the file. This ensures
-		// that we mark this package as non-redistributable.
-		if licenseTypes[i] != "" {
-			md.Types = append(md.Types, licenseTypes[i])
-		}
-	}
-	sort.Slice(mds, func(i, j int) bool {
-		return compareLicenses(mds[i], mds[j])
-	})
-	return mds, nil
-}
-
-// compareLicenses reports whether i < j according to our license sorting
-// semantics.
-func compareLicenses(i, j *licenses.Metadata) bool {
-	if len(strings.Split(i.FilePath, "/")) > len(strings.Split(j.FilePath, "/")) {
-		return true
-	}
-	return i.FilePath < j.FilePath
-}
-
 // GetModuleInfo fetches a Version from the database with the primary key
 // (module_path, version).
 func (db *DB) GetModuleInfo(ctx context.Context, modulePath string, version string) (_ *internal.LegacyModuleInfo, err error) {
diff --git a/internal/postgres/details_test.go b/internal/postgres/details_test.go
index 1b14baf..3181633 100644
--- a/internal/postgres/details_test.go
+++ b/internal/postgres/details_test.go
@@ -353,82 +353,6 @@
 	}
 }
 
-func TestGetPackageLicenses(t *testing.T) {
-	modulePath := "test.module"
-	testModule := sample.Module(modulePath, "v1.2.3", "", "foo")
-	testModule.LegacyPackages[0].Licenses = nil
-	testModule.LegacyPackages[1].Licenses = sample.LicenseMetadata
-
-	tests := []struct {
-		label, pkgPath string
-		wantLicenses   []*licenses.License
-	}{
-		{
-			label:        "package with licenses",
-			pkgPath:      "test.module/foo",
-			wantLicenses: sample.Licenses,
-		}, {
-			label:        "package with no licenses",
-			pkgPath:      "test.module",
-			wantLicenses: nil,
-		},
-	}
-
-	defer ResetTestDB(testDB, t)
-	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
-	defer cancel()
-
-	if err := testDB.InsertModule(ctx, testModule); err != nil {
-		t.Fatal(err)
-	}
-
-	for _, test := range tests {
-		t.Run(test.label, func(t *testing.T) {
-			got, err := testDB.GetPackageLicenses(ctx, test.pkgPath, modulePath, testModule.Version)
-			if err != nil {
-				t.Fatal(err)
-			}
-			if diff := cmp.Diff(test.wantLicenses, got); diff != "" {
-				t.Errorf("testDB.GetLicenses(ctx, %q, %q) mismatch (-want +got):\n%s", test.pkgPath, testModule.Version, diff)
-			}
-		})
-	}
-}
-
-func TestGetModuleLicenses(t *testing.T) {
-	modulePath := "test.module"
-	testModule := sample.Module(modulePath, "v1.2.3", "", "foo", "bar")
-	testModule.LegacyPackages[0].Licenses = []*licenses.Metadata{{Types: []string{"ISC"}, FilePath: "LICENSE"}}
-	testModule.LegacyPackages[1].Licenses = []*licenses.Metadata{{Types: []string{"MIT"}, FilePath: "foo/LICENSE"}}
-	testModule.LegacyPackages[2].Licenses = []*licenses.Metadata{{Types: []string{"GPL2"}, FilePath: "bar/LICENSE.txt"}}
-
-	defer ResetTestDB(testDB, t)
-	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
-	defer cancel()
-
-	testModule.Licenses = nil
-	for _, p := range testModule.LegacyPackages {
-		testModule.Licenses = append(testModule.Licenses, &licenses.License{
-			Metadata: p.Licenses[0],
-			Contents: []byte(`Lorem Ipsum`),
-		})
-	}
-
-	if err := testDB.InsertModule(ctx, testModule); err != nil {
-		t.Fatal(err)
-	}
-
-	got, err := testDB.GetModuleLicenses(ctx, modulePath, testModule.Version)
-	if err != nil {
-		t.Fatal(err)
-	}
-	// We only want the top-level license.
-	wantLicenses := []*licenses.License{testModule.Licenses[0]}
-	if diff := cmp.Diff(wantLicenses, got); diff != "" {
-		t.Errorf("testDB.GetModuleLicenses(ctx, %q, %q) mismatch (-want +got):\n%s", modulePath, testModule.Version, diff)
-	}
-}
-
 func TestJSONBScanner(t *testing.T) {
 	type S struct{ A int }
 
diff --git a/internal/postgres/licenses.go b/internal/postgres/licenses.go
new file mode 100644
index 0000000..859027b
--- /dev/null
+++ b/internal/postgres/licenses.go
@@ -0,0 +1,159 @@
+// Copyright 2020 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 postgres
+
+import (
+	"context"
+	"database/sql"
+	"fmt"
+	"reflect"
+	"sort"
+	"strings"
+
+	"github.com/lib/pq"
+	"golang.org/x/pkgsite/internal/derrors"
+	"golang.org/x/pkgsite/internal/licenses"
+)
+
+// GetModuleLicenses returns all licenses associated with the given module path and
+// version. These are the top-level licenses in the module zip file.
+// It returns an InvalidArgument error if the module path or version is invalid.
+func (db *DB) GetModuleLicenses(ctx context.Context, modulePath, version string) (_ []*licenses.License, err error) {
+	defer derrors.Wrap(&err, "GetModuleLicenses(ctx, %q, %q)", modulePath, version)
+
+	if modulePath == "" || version == "" {
+		return nil, fmt.Errorf("neither modulePath nor version can be empty: %w", derrors.InvalidArgument)
+	}
+	query := `
+	SELECT
+		types, file_path, contents, coverage
+	FROM
+		licenses
+	WHERE
+		module_path = $1 AND version = $2 AND position('/' in file_path) = 0
+    `
+	rows, err := db.db.Query(ctx, query, modulePath, version)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	return collectLicenses(rows)
+}
+
+// GetPackageLicenses returns all licenses associated with the given package path and
+// version.
+// It returns an InvalidArgument error if the module path or version is invalid.
+func (db *DB) GetPackageLicenses(ctx context.Context, pkgPath, modulePath, version string) (_ []*licenses.License, err error) {
+	defer derrors.Wrap(&err, "GetPackageLicenses(ctx, %q, %q, %q)", pkgPath, modulePath, version)
+
+	if pkgPath == "" || version == "" {
+		return nil, fmt.Errorf("neither pkgPath nor version can be empty: %w", derrors.InvalidArgument)
+	}
+	query := `
+		SELECT
+			l.types,
+			l.file_path,
+			l.contents,
+			l.coverage
+		FROM
+			licenses l
+		INNER JOIN (
+			SELECT DISTINCT ON (license_file_path)
+				module_path,
+				version,
+				unnest(license_paths) AS license_file_path
+			FROM
+				packages
+			WHERE
+				path = $1
+				AND module_path = $2
+				AND version = $3
+		) p
+		ON
+			p.module_path = l.module_path
+			AND p.version = l.version
+			AND p.license_file_path = l.file_path;`
+
+	rows, err := db.db.Query(ctx, query, pkgPath, modulePath, version)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	return collectLicenses(rows)
+}
+
+// collectLicenses converts the sql rows to a list of licenses. The columns
+// must be types, file_path and contents, in that order.
+func collectLicenses(rows *sql.Rows) ([]*licenses.License, error) {
+	mustHaveColumns(rows, "types", "file_path", "contents", "coverage")
+	var lics []*licenses.License
+	for rows.Next() {
+		var (
+			lic          = &licenses.License{Metadata: &licenses.Metadata{}}
+			licenseTypes []string
+		)
+		if err := rows.Scan(pq.Array(&licenseTypes), &lic.FilePath, &lic.Contents, jsonbScanner{&lic.Coverage}); err != nil {
+			return nil, fmt.Errorf("row.Scan(): %v", err)
+		}
+		lic.Types = licenseTypes
+		lics = append(lics, lic)
+	}
+	sort.Slice(lics, func(i, j int) bool {
+		return compareLicenses(lics[i].Metadata, lics[j].Metadata)
+	})
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+	return lics, nil
+}
+
+// mustHaveColumns panics if the columns of rows does not match wantColumns.
+func mustHaveColumns(rows *sql.Rows, wantColumns ...string) {
+	gotColumns, err := rows.Columns()
+	if err != nil {
+		panic(err)
+	}
+	if !reflect.DeepEqual(gotColumns, wantColumns) {
+		panic(fmt.Sprintf("got columns %v, want $%v", gotColumns, wantColumns))
+	}
+}
+
+// zipLicenseMetadata constructs licenses.Metadata from the given license types
+// and paths, by zipping and then sorting.
+func zipLicenseMetadata(licenseTypes []string, licensePaths []string) (_ []*licenses.Metadata, err error) {
+	defer derrors.Wrap(&err, "zipLicenseMetadata(%v, %v)", licenseTypes, licensePaths)
+
+	if len(licenseTypes) != len(licensePaths) {
+		return nil, fmt.Errorf("BUG: got %d license types and %d license paths", len(licenseTypes), len(licensePaths))
+	}
+	byPath := make(map[string]*licenses.Metadata)
+	var mds []*licenses.Metadata
+	for i, p := range licensePaths {
+		md, ok := byPath[p]
+		if !ok {
+			md = &licenses.Metadata{FilePath: p}
+			mds = append(mds, md)
+		}
+		// By convention, we insert a license path with empty corresponding license
+		// type if we are unable to detect *any* licenses in the file. This ensures
+		// that we mark this package as non-redistributable.
+		if licenseTypes[i] != "" {
+			md.Types = append(md.Types, licenseTypes[i])
+		}
+	}
+	sort.Slice(mds, func(i, j int) bool {
+		return compareLicenses(mds[i], mds[j])
+	})
+	return mds, nil
+}
+
+// compareLicenses reports whether i < j according to our license sorting
+// semantics.
+func compareLicenses(i, j *licenses.Metadata) bool {
+	if len(strings.Split(i.FilePath, "/")) > len(strings.Split(j.FilePath, "/")) {
+		return true
+	}
+	return i.FilePath < j.FilePath
+}
diff --git a/internal/postgres/licenses_test.go b/internal/postgres/licenses_test.go
new file mode 100644
index 0000000..86abe48
--- /dev/null
+++ b/internal/postgres/licenses_test.go
@@ -0,0 +1,90 @@
+// Copyright 2020 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 postgres
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"golang.org/x/pkgsite/internal/licenses"
+	"golang.org/x/pkgsite/internal/testing/sample"
+)
+
+func TestGetModuleLicenses(t *testing.T) {
+	modulePath := "test.module"
+	testModule := sample.Module(modulePath, "v1.2.3", "", "foo", "bar")
+	testModule.LegacyPackages[0].Licenses = []*licenses.Metadata{{Types: []string{"ISC"}, FilePath: "LICENSE"}}
+	testModule.LegacyPackages[1].Licenses = []*licenses.Metadata{{Types: []string{"MIT"}, FilePath: "foo/LICENSE"}}
+	testModule.LegacyPackages[2].Licenses = []*licenses.Metadata{{Types: []string{"GPL2"}, FilePath: "bar/LICENSE.txt"}}
+
+	defer ResetTestDB(testDB, t)
+	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+	defer cancel()
+
+	testModule.Licenses = nil
+	for _, p := range testModule.LegacyPackages {
+		testModule.Licenses = append(testModule.Licenses, &licenses.License{
+			Metadata: p.Licenses[0],
+			Contents: []byte(`Lorem Ipsum`),
+		})
+	}
+
+	if err := testDB.InsertModule(ctx, testModule); err != nil {
+		t.Fatal(err)
+	}
+
+	got, err := testDB.GetModuleLicenses(ctx, modulePath, testModule.Version)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// We only want the top-level license.
+	wantLicenses := []*licenses.License{testModule.Licenses[0]}
+	if diff := cmp.Diff(wantLicenses, got); diff != "" {
+		t.Errorf("testDB.GetModuleLicenses(ctx, %q, %q) mismatch (-want +got):\n%s", modulePath, testModule.Version, diff)
+	}
+}
+
+func TestGetPackageLicenses(t *testing.T) {
+	modulePath := "test.module"
+	testModule := sample.Module(modulePath, "v1.2.3", "", "foo")
+	testModule.LegacyPackages[0].Licenses = nil
+	testModule.LegacyPackages[1].Licenses = sample.LicenseMetadata
+
+	tests := []struct {
+		label, pkgPath string
+		wantLicenses   []*licenses.License
+	}{
+		{
+			label:        "package with licenses",
+			pkgPath:      "test.module/foo",
+			wantLicenses: sample.Licenses,
+		}, {
+			label:        "package with no licenses",
+			pkgPath:      "test.module",
+			wantLicenses: nil,
+		},
+	}
+
+	defer ResetTestDB(testDB, t)
+	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+	defer cancel()
+
+	if err := testDB.InsertModule(ctx, testModule); err != nil {
+		t.Fatal(err)
+	}
+
+	for _, test := range tests {
+		t.Run(test.label, func(t *testing.T) {
+			got, err := testDB.GetPackageLicenses(ctx, test.pkgPath, modulePath, testModule.Version)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if diff := cmp.Diff(test.wantLicenses, got); diff != "" {
+				t.Errorf("testDB.GetLicenses(ctx, %q, %q) mismatch (-want +got):\n%s", test.pkgPath, testModule.Version, diff)
+			}
+		})
+	}
+}