internal/postgres: save and restore new licensecheck Coverage

Add a NewCoverage field internal/licenses.Metadata to support
the upcoming change to the new licensecheck.

When saving to the DB, save the NewCoverage field if it is populated;
otherwise save the original Coverage field.

When reading the JSON for the coverage field from the DB, distinguish
old and new Coverage structs and populate one of the two fields of the
licenses.Metadata value as appropriate.

Change-Id: I5be5623fbaec668265e1e30a878864cf4aef2ae6
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/283652
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/internal/licenses/licenses.go b/internal/licenses/licenses.go
index c8f9f39..5940cf4 100644
--- a/internal/licenses/licenses.go
+++ b/internal/licenses/licenses.go
@@ -28,6 +28,7 @@
 	"sort"
 	"strings"
 
+	"github.com/google/licensecheck"
 	oldlicensecheck "github.com/google/licensecheck/old"
 	"golang.org/x/mod/module"
 	modzip "golang.org/x/mod/zip"
@@ -63,7 +64,8 @@
 	// relative to the contents directory.
 	FilePath string
 	// The output of oldlicensecheck.Cover.
-	Coverage oldlicensecheck.Coverage
+	Coverage    oldlicensecheck.Coverage
+	NewCoverage licensecheck.Coverage
 }
 
 // A License is a classified license file path and its contents.
diff --git a/internal/postgres/insert_module.go b/internal/postgres/insert_module.go
index e26c251..563e90c 100644
--- a/internal/postgres/insert_module.go
+++ b/internal/postgres/insert_module.go
@@ -207,9 +207,17 @@
 	defer derrors.Wrap(&err, "insertLicenses(ctx, %q, %q)", m.ModulePath, m.Version)
 	var licenseValues []interface{}
 	for _, l := range m.Licenses {
-		covJSON, err := json.Marshal(l.Coverage)
-		if err != nil {
-			return fmt.Errorf("marshalling %+v: %v", l.Coverage, err)
+		var covJSON []byte
+		if l.NewCoverage.Percent == 0 && l.NewCoverage.Match == nil {
+			covJSON, err = json.Marshal(l.Coverage)
+			if err != nil {
+				return fmt.Errorf("marshalling %+v: %v", l.Coverage, err)
+			}
+		} else {
+			covJSON, err = json.Marshal(l.NewCoverage)
+			if err != nil {
+				return fmt.Errorf("marshalling %+v: %v", l.NewCoverage, err)
+			}
 		}
 		licenseValues = append(licenseValues, l.FilePath,
 			makeValidUnicode(string(l.Contents)), pq.Array(l.Types), covJSON,
diff --git a/internal/postgres/insert_module_test.go b/internal/postgres/insert_module_test.go
index 3b2f429..8921a65 100644
--- a/internal/postgres/insert_module_test.go
+++ b/internal/postgres/insert_module_test.go
@@ -18,6 +18,7 @@
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
+	"github.com/google/licensecheck"
 	"github.com/google/safehtml"
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/database"
@@ -259,6 +260,45 @@
 	}
 }
 
+func TestInsertModuleNewCoverage(t *testing.T) {
+	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+	defer cancel()
+	defer ResetTestDB(testDB, t)
+
+	m := sample.DefaultModule()
+	newCoverage := licensecheck.Coverage{
+		Percent: 100,
+		Match:   []licensecheck.Match{{ID: "New", Start: 1, End: 10}},
+	}
+	m.Licenses = []*licenses.License{
+		{
+			Metadata: &licenses.Metadata{
+				Types:       []string{sample.LicenseType},
+				FilePath:    sample.LicenseFilePath,
+				NewCoverage: newCoverage,
+			},
+			Contents: []byte(`Lorem Ipsum`),
+		},
+	}
+	if err := testDB.InsertModule(ctx, m); err != nil {
+		t.Fatal(err)
+	}
+	u, err := testDB.GetUnit(ctx, &internal.UnitMeta{Path: m.ModulePath, ModulePath: m.ModulePath, Version: m.Version}, internal.AllFields)
+	if err != nil {
+		t.Fatal(err)
+	}
+	got := u.LicenseContents[0].Metadata
+	want := &licenses.Metadata{
+		Types:       []string{"MIT"},
+		FilePath:    sample.LicenseFilePath,
+		NewCoverage: newCoverage,
+	}
+	if !cmp.Equal(got, want) {
+		t.Errorf("\ngot  %+v\nwant %+v", got, want)
+	}
+
+}
+
 func TestPostgres_ReadAndWriteModuleOtherColumns(t *testing.T) {
 	// Verify that InsertModule correctly populates the columns in the versions
 	// table that are not in the ModuleInfo struct.
diff --git a/internal/postgres/licenses.go b/internal/postgres/licenses.go
index 87755a9..a3a8ec7 100644
--- a/internal/postgres/licenses.go
+++ b/internal/postgres/licenses.go
@@ -7,12 +7,14 @@
 import (
 	"context"
 	"database/sql"
+	"encoding/json"
 	"fmt"
 	"path"
 	"reflect"
 	"sort"
 	"strings"
 
+	"github.com/google/licensecheck"
 	"github.com/lib/pq"
 	"golang.org/x/pkgsite/internal/derrors"
 	"golang.org/x/pkgsite/internal/licenses"
@@ -108,10 +110,25 @@
 		var (
 			lic          = &licenses.License{Metadata: &licenses.Metadata{}}
 			licenseTypes []string
+			covBytes     []byte
 		)
-		if err := rows.Scan(pq.Array(&licenseTypes), &lic.FilePath, &lic.Contents, jsonbScanner{&lic.Coverage}); err != nil {
+		if err := rows.Scan(pq.Array(&licenseTypes), &lic.FilePath, &lic.Contents, &covBytes); err != nil {
 			return nil, fmt.Errorf("row.Scan(): %v", err)
 		}
+		// The coverage column is JSON for either the new or old
+		// licensecheck.Coverage struct. The new Match type has an ID field
+		// which is always populated, but the old one doesn't. First try
+		// unmarshalling the new one, then if that doesn't populate the ID
+		// field, try the old.
+		if err := json.Unmarshal(covBytes, &lic.NewCoverage); err != nil {
+			return nil, err
+		}
+		if len(lic.NewCoverage.Match) == 0 || lic.NewCoverage.Match[0].ID == "" {
+			lic.NewCoverage = licensecheck.Coverage{}
+			if err := json.Unmarshal(covBytes, &lic.Coverage); err != nil {
+				return nil, err
+			}
+		}
 		lic.Types = licenseTypes
 		if !bypassLicenseCheck {
 			lic.RemoveNonRedistributableData()