Guard cargo publishes against name and version collisions
- serialise publishes per crate with an advisory lock in one transaction - add a unique index on normalised name and version without build metadata - validate versions as strict SemVer 2.0 and reject an empty .crate - renumber the migration to 0005
This commit is contained in:
@@ -2,7 +2,10 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -460,3 +463,80 @@ func TestCargoIndex(t *testing.T) {
|
||||
t.Fatalf("after delete: %+v", entries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCargoIndexPublishConflicts(t *testing.T) {
|
||||
requireDB(t)
|
||||
entry := func(name, version string) *provider.CargoIndexEntry {
|
||||
return &provider.CargoIndexEntry{
|
||||
RepoName: "cargo-conflict", FilePath: "crates/" + name + "/" + name + "-" + version + ".crate",
|
||||
Name: name, Version: version, Line: "{}",
|
||||
}
|
||||
}
|
||||
if err := testDB.InsertCargoIndexEntry(ctx(), entry("foo-bar", "1.0.0+a")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name, version string
|
||||
want error
|
||||
}{
|
||||
{"foo-bar", "1.0.0+b", ErrAlreadyExists},
|
||||
{"foo-bar", "1.0.0", ErrAlreadyExists},
|
||||
{"Foo-Bar", "2.0.0", ErrCargoNameTaken},
|
||||
{"foo_bar", "2.0.0", ErrCargoNameTaken},
|
||||
} {
|
||||
if err := testDB.InsertCargoIndexEntry(ctx(), entry(tc.name, tc.version)); !errors.Is(err, tc.want) {
|
||||
t.Errorf("%s@%s: err = %v, want %v", tc.name, tc.version, err, tc.want)
|
||||
}
|
||||
}
|
||||
if err := testDB.InsertCargoIndexEntry(ctx(), entry("foo-bar", "1.0.1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCargoIndexConcurrentPublish(t *testing.T) {
|
||||
requireDB(t)
|
||||
names := []string{"Race", "race", "RACE", "race", "rAce", "RaCe", "racE", "race"}
|
||||
errs := make(chan error, len(names))
|
||||
var wg sync.WaitGroup
|
||||
for i, name := range names {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errs <- testDB.InsertCargoIndexEntry(ctx(), &provider.CargoIndexEntry{
|
||||
RepoName: "cargo-race", FilePath: fmt.Sprintf("crates/%s/%d.crate", name, i),
|
||||
Name: name, Version: "1.0.0", Line: "{}",
|
||||
})
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(errs)
|
||||
ok := 0
|
||||
for err := range errs {
|
||||
switch {
|
||||
case err == nil:
|
||||
ok++
|
||||
case !errors.Is(err, ErrAlreadyExists) && !errors.Is(err, ErrCargoNameTaken):
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
if ok != 1 {
|
||||
t.Fatalf("%d concurrent publishes succeeded, want 1", ok)
|
||||
}
|
||||
if entries, _ := testDB.ListCargoIndexEntries(ctx(), "cargo-race", "race"); len(entries) > 1 {
|
||||
t.Fatalf("entries = %+v", entries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCargoIndexUniqueIndex(t *testing.T) {
|
||||
requireDB(t)
|
||||
insert := func(name, version, path string) error {
|
||||
_, err := testDB.Pool.Exec(ctx(), `INSERT INTO cargo_index (repo_name, file_path, name, version, line) VALUES ('cargo-uniq', $1, $2, $3, '{}')`, path, name, version)
|
||||
return err
|
||||
}
|
||||
if err := insert("a-b", "1.0.0+x", "p1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := insert("A_B", "1.0.0+y", "p2"); err == nil {
|
||||
t.Fatal("unique index allowed a normalised duplicate")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user