package oltp
import (
"sync"
"testing"
"craftdesign.group/quarry/internal/xfer"
)
// An account whose only token resolves to no push letter has no push
// credential, but its token still proves it: LetterlessSecrets answers
// that token's secret so a card signed with it can be told what is
// missing. A name that is not an account, and a disabled account, have
// nothing to answer.
func TestLetterlessSecretsAreTheTokensThatSignNoPush(t *testing.T) {
db := tokenDB(t, "sandbox")
if err := db.AddAccount("pat", "irrelevant to pushing", false); err != nil {
t.Fatal(err)
}
if err := db.Grant("pat", "sandbox", Read); err != nil {
t.Fatal(err)
}
if _, err := db.MintSyncToken("pat"); err != nil {
t.Fatal(err)
}
if got := db.Secrets("sandbox", "pat"); len(got) != 0 {
t.Fatalf("FAIL: a reader's token signs a push: %+v", got)
}
got := db.LetterlessSecrets("sandbox", "pat")
if len(got) != 1 || got[0].CheckIn || got[0].Publish || got[0].Secret == "" {
t.Fatalf("FAIL: the reader's token is not offered as letterless: %+v", got)
}
if got := db.LetterlessSecrets("sandbox", "nobody-here"); len(got) != 0 {
t.Errorf("FAIL: an unknown name has credentials: %+v", got)
}
// An account that may push has its token in Secrets, not here.
writerWithToken(t, db, "jan", "sandbox")
if got := db.LetterlessSecrets("sandbox", "jan"); len(got) != 0 {
t.Errorf("FAIL: a pushing token is offered as letterless: %+v", got)
}
if err := db.SetDisabled("pat", true); err != nil {
t.Fatal(err)
}
if got := db.LetterlessSecrets("sandbox", "pat"); len(got) != 0 {
t.Errorf("FAIL: a disabled account has credentials: %+v", got)
}
t.Log("ok")
}
// SecretsAt is Secrets with the secret derived from the code given: the
// same credentials, the same letters, a different code, and nothing at
// all for an empty code, a run's name, a disabled account or a name that
// is no account.
func TestSecretsAtDerivesFromTheGivenCodeAndChangesNothingElse(t *testing.T) {
db := tokenDB(t, "sandbox")
writerWithToken(t, db, "jan", "sandbox")
base := db.Secrets("sandbox", "jan")
if len(base) != 1 {
t.Fatalf("FAIL: want one credential, got %+v", base)
}
other := "0123456789abcdef0123456789abcdef01234567"
at := db.SecretsAt("sandbox", "jan", other)
if len(at) != 1 || at[0].Secret == base[0].Secret || at[0].Token != base[0].Token ||
at[0].CheckIn != base[0].CheckIn || at[0].Publish != base[0].Publish {
t.Errorf("FAIL: SecretsAt = %+v beside %+v", at, base)
}
same, _ := db.SyncProjectCode("sandbox")
if got := db.SecretsAt("sandbox", "jan", same); len(got) != 1 || got[0].Secret != base[0].Secret {
t.Errorf("FAIL: the store's own code derives %+v, not %+v", got, base)
}
for name, got := range map[string][]xfer.Credential{
"empty code": db.SecretsAt("sandbox", "jan", ""),
"run name": db.SecretsAt("sandbox", "run-x", other),
"no account": db.SecretsAt("sandbox", "nobody-here", other),
} {
if len(got) != 0 {
t.Errorf("FAIL: %s derives %+v", name, got)
}
}
if err := db.SetDisabled("jan", true); err != nil {
t.Fatal(err)
}
if got := db.SecretsAt("sandbox", "jan", other); len(got) != 0 {
t.Errorf("FAIL: a disabled account derives %+v", got)
}
if !t.Failed() {
t.Log("ok")
}
}
// A claim on a project code is one statement that succeeds only while no
// other repository has the code or lacks one to compare; repositories
// that already share a code keep sharing it.
func TestClaimProjectCode(t *testing.T) {
db := tokenDB(t, "a", "b", "c")
if got, err := db.ClaimProjectCode("a", "X"); err != nil || got != CodeUnknown {
t.Fatalf("FAIL: with other rows recording no code, claim = %v, %v; want CodeUnknown", got, err)
}
for _, r := range []string{"b", "c"} {
if err := db.SetRepoCode(r, "SHARED"); err != nil {
t.Fatal(err)
}
}
if err := db.SetRepoCode("a", "OWN"); err != nil {
t.Fatal(err)
}
if got, _ := db.ClaimProjectCode("a", "SHARED"); got != CodeTaken {
t.Errorf("FAIL: a code two repositories hold was claimed: %v", got)
}
if got, err := db.ClaimProjectCode("a", "NEW"); err != nil || got != CodeClaimed {
t.Fatalf("FAIL: an unused code was %v, %v", got, err)
}
if got, _ := db.RepoCode("a"); got != "NEW" {
t.Errorf("FAIL: the row records %q", got)
}
// b and c share a code and are untouched by it.
if got, _ := db.RepoCode("b"); got != "SHARED" {
t.Errorf("FAIL: an existing shared code moved: %q", got)
}
// Taken now, by a; and given back by the store's answer.
if got, _ := db.ClaimProjectCode("b", "NEW"); got != CodeTaken {
t.Errorf("FAIL: a claimed code was claimed again: %v", got)
}
if err := db.SetRepoCode("a", "OWN"); err != nil {
t.Fatal(err)
}
if got, _ := db.ClaimProjectCode("b", "NEW"); got != CodeClaimed {
t.Errorf("FAIL: a released code was %v", got)
}
if got, _ := db.ClaimProjectCode("nobody", "Z"); got == CodeClaimed {
t.Errorf("FAIL: a repository that is not registered claimed a code")
}
if got, _ := db.ClaimProjectCode("a", ""); got == CodeClaimed {
t.Errorf("FAIL: an empty code was claimed")
}
if !t.Failed() {
t.Log("ok")
}
}
// Two repositories claiming one code at once: exactly one wins.
func TestConcurrentClaimsHaveOneWinner(t *testing.T) {
db := tokenDB(t, "a", "b")
for _, r := range []string{"a", "b"} {
if err := db.SetRepoCode(r, "own-"+r); err != nil {
t.Fatal(err)
}
}
var wg sync.WaitGroup
won := make(chan string, 2)
for _, r := range []string{"a", "b"} {
wg.Add(1)
go func() {
defer wg.Done()
if got, err := db.ClaimProjectCode(r, "SAME"); err == nil && got == CodeClaimed {
won <- r
}
}()
}
wg.Wait()
close(won)
if n := len(won); n != 1 {
t.Errorf("FAIL: %d repositories won one code", n)
}
if !t.Failed() {
t.Log("ok")
}
}
// Only a repository that could hold a code it has not recorded blocks a
// claim: a Failed row is a record and a Cloning one is let through, while
// a Ready one with no code blocks until it is backfilled.
func TestClaimIgnoresFailedAndCloningRowsButNotUnrecordedReadyOnes(t *testing.T) {
db := tokenDB(t, "mine", "failed", "cloning", "ready")
if err := db.SetRepoState("failed", Failed, "x"); err != nil {
t.Fatal(err)
}
if err := db.SetRepoState("cloning", Cloning, ""); err != nil {
t.Fatal(err)
}
if got, _ := db.ClaimProjectCode("mine", "NEW"); got != CodeUnknown {
t.Fatalf("FAIL: a Ready row with no code did not block the claim: %v", got)
}
rows, err := db.ReadyWithoutCode()
if err != nil || len(rows) != 2 || rows["ready"] == "" || rows["mine"] == "" {
t.Fatalf("FAIL: ReadyWithoutCode = %v, %v", rows, err)
}
if err := db.SetRepoCode("ready", "OTHER"); err != nil {
t.Fatal(err)
}
if got, err := db.ClaimProjectCode("mine", "NEW"); err != nil || got != CodeClaimed {
t.Errorf("FAIL: with only Failed and Cloning rows unrecorded the claim was %v, %v", got, err)
}
if n, _ := db.CodeHeldElsewhere("ready", "NEW"); n != 1 {
t.Errorf("FAIL: CodeHeldElsewhere = %d, want 1", n)
}
if !t.Failed() {
t.Log("ok")
}
}
// A private import still cloning blocks a claim, since its channel would
// otherwise be shared with whoever claimed its code meanwhile; a public
// one, or a failed one of either, does not.
func TestAPrivateCloningRowBlocksAClaim(t *testing.T) {
db := tokenDB(t, "mine", "other")
if err := db.SetRepoCode("other", "OTHER"); err != nil {
t.Fatal(err)
}
if err := db.SetRepoCode("mine", "OWN"); err != nil {
t.Fatal(err)
}
add := func(name, vis, state string) {
t.Helper()
if err := db.AddRepo(name, t.TempDir(), "", vis); err != nil {
t.Fatal(err)
}
if err := db.SetRepoState(name, state, "x"); err != nil {
t.Fatal(err)
}
}
add("pubclone", Public, Cloning)
add("privfail", Private, Failed)
if got, err := db.ClaimProjectCode("mine", "A"); err != nil || got != CodeClaimed {
t.Fatalf("FAIL: a public Cloning and a private Failed row blocked a claim: %v, %v", got, err)
}
add("privclone", Private, Cloning)
if got, _ := db.ClaimProjectCode("mine", "B"); got != CodeUnknown {
t.Errorf("FAIL: a private Cloning row did not block a claim: %v", got)
}
if err := db.SetRepoState("privclone", Ready, ""); err != nil {
t.Fatal(err)
}
if err := db.SetRepoCode("privclone", "P"); err != nil {
t.Fatal(err)
}
if got, _ := db.ClaimProjectCode("mine", "B"); got != CodeClaimed {
t.Errorf("FAIL: a recorded Ready row still blocked: %v", got)
}
if !t.Failed() {
t.Log("ok")
}
}