Skip to content
quarry Artifact [fe1a22d9e5]
Login

Artifact [fe1a22d9e5]

Artifact fe1a22d9e507fcb58e47756b808522a248135e6273a03fd008ea97e637c3fc6e:


package oltp

import (
	"path/filepath"
	"sync"
	"testing"
	"time"
)

func openSetupStartDB(t *testing.T) *DB {
	t.Helper()
	db, err := Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	return db
}

func TestASetupStartIsHeldHashedClaimedOnceAndLapses(t *testing.T) {
	db := openSetupStartDB(t)
	now := time.Now()
	pick := "abcd"
	code, err := db.StartSetup(SetupStart{Pickup: pick, Name: "e-desktop", Kind: "read", Addr: "192.0.2.1"}, 10, 100, now)
	if err != nil {
		t.Fatal(err)
	}
	if len(code) != 8 {
		t.Fatalf("FAIL: code %q is not eight characters", code)
	}
	var stored int
	if err := db.db.QueryRow(`SELECT count(*) FROM setup_start WHERE hash = ? OR hash LIKE ?`, code, "%"+code+"%").Scan(&stored); err != nil || stored != 0 {
		t.Errorf("FAIL: the short code is stored in the clear (%d, %v)", stored, err)
	}
	// Typed any way a person might: lower case, dashes, look-alikes.
	typed := code[:4] + "-" + code[4:]
	if s, ok, err := db.SetupStartByCode(typed, now); err != nil || !ok || s.Name != "e-desktop" || s.Kind != "read" || s.Addr != "192.0.2.1" || s.Pickup != pick {
		t.Errorf("FAIL: the start by its code: %+v %v %v", s, ok, err)
	}
	// Two claims at once: exactly one gets the row.
	var wg sync.WaitGroup
	var mu sync.Mutex
	won := 0
	for i := 0; i < 8; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if _, ok, err := db.ClaimSetupStart(code, now); err == nil && ok {
				mu.Lock()
				won++
				mu.Unlock()
			}
		}()
	}
	wg.Wait()
	if won != 1 {
		t.Errorf("FAIL: %d claims won", won)
	}
	if _, ok, _ := db.SetupStartByCode(code, now); ok {
		t.Errorf("FAIL: a claimed start is still held")
	}

	// A lapsed start is not found, and the next write removes the row.
	old := now.Add(-SetupStartLife - time.Minute)
	lapsed, err := db.StartSetup(SetupStart{Pickup: pick, Name: "x", Kind: "push", Addr: "a"}, 10, 100, old)
	if err != nil {
		t.Fatal(err)
	}
	if _, ok, _ := db.SetupStartByCode(lapsed, now); ok {
		t.Errorf("FAIL: a lapsed start is found")
	}
	if n, _ := db.SetupStartRows(); n != 1 {
		t.Fatalf("FAIL: %d rows before the sweep", n)
	}
	if _, err := db.StartSetup(SetupStart{Pickup: pick, Name: "y", Kind: "push", Addr: "a"}, 10, 100, now); err != nil {
		t.Fatal(err)
	}
	if n, _ := db.SetupStartRows(); n != 1 {
		t.Errorf("FAIL: the lapsed row was not removed: %d rows", n)
	}

	// A start put back after a claim is as it was.
	c2, _ := db.StartSetup(SetupStart{Pickup: pick, Name: "z", Kind: "read", Addr: "a"}, 10, 100, now)
	held, ok, _ := db.ClaimSetupStart(c2, now)
	if !ok {
		t.Fatal("FAIL: claim")
	}
	if err := db.RestoreSetupStart(c2, held); err != nil {
		t.Fatal(err)
	}
	if s, ok, _ := db.SetupStartByCode(c2, now); !ok || s != held {
		t.Errorf("FAIL: the restored start differs: %+v", s)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestSetupStartsAreCapped(t *testing.T) {
	db := openSetupStartDB(t)
	now := time.Now()
	for i := 0; i < 3; i++ {
		if _, err := db.StartSetup(SetupStart{Pickup: "p", Name: "n", Kind: "read", Addr: "a"}, 3, 100, now); err != nil {
			t.Fatal(err)
		}
	}
	if _, err := db.StartSetup(SetupStart{Pickup: "p", Name: "n", Kind: "read", Addr: "a"}, 3, 100, now); err != ErrSetupStartsFull {
		t.Errorf("FAIL: past the cap: %v", err)
	}
	// Lapsed ones do not count against it.
	if _, err := db.StartSetup(SetupStart{Pickup: "p", Name: "n", Kind: "read", Addr: "a"}, 3, 100, now.Add(SetupStartLife+time.Minute)); err != nil {
		t.Errorf("FAIL: lapsed starts held the cap: %v", err)
	}
}

func TestSetupCodesAreReadTheWayTheyAreTyped(t *testing.T) {
	for typed, want := range map[string]string{"abcd-efgh": "ABCDEFGH", "0o1i-lL22": "00111122"} {
		if got, ok := NormalizeSetupCode(typed); !ok || got != want {
			t.Errorf("FAIL: %q reads as %q (%v), want %q", typed, got, ok, want)
		}
	}
	for _, bad := range []string{"", "ABC", "ABCD-EFGHJ", "ABCD-EFGU", "ABCD EFG!"} {
		if _, ok := NormalizeSetupCode(bad); ok {
			t.Errorf("FAIL: %q is read as a code", bad)
		}
	}
}