internal/store/store_test.go

e3632a550366fe23b2619edba30fc58fab19b598
gitbay/internal/store/store_test.go history · blame · raw

117 lines · 3174 bytes

  1package store
  2
  3import (
  4	"os"
  5	"path/filepath"
  6	"strings"
  7	"testing"
  8)
  9
 10func open(t *testing.T) *Store {
 11	t.Helper()
 12	s, err := Open(filepath.Join(t.TempDir(), "gitbay.db"))
 13	if err != nil {
 14		t.Fatal(err)
 15	}
 16	t.Cleanup(func() { s.Close() })
 17	return s
 18}
 19
 20func TestMigrateUpDown(t *testing.T) {
 21	s := open(t)
 22
 23	if err := s.MigrateUp(); err != nil {
 24		t.Fatal(err)
 25	}
 26	v, err := s.Version()
 27	if err != nil {
 28		t.Fatal(err)
 29	}
 30	if v < 1 {
 31		t.Fatalf("version %d after MigrateUp", v)
 32	}
 33
 34	// Seeded settings row exists.
 35	var epoch string
 36	if err := s.DB.QueryRow("SELECT value FROM settings WHERE key = 'key_epoch'").Scan(&epoch); err != nil {
 37		t.Fatal(err)
 38	}
 39	if epoch != "1" {
 40		t.Fatalf("key_epoch = %q, want 1", epoch)
 41	}
 42
 43	// Down to empty, then back up.
 44	if err := s.MigrateTo(0); err != nil {
 45		t.Fatal(err)
 46	}
 47	var n int
 48	if err := s.DB.QueryRow("SELECT count(*) FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").Scan(&n); err != nil {
 49		t.Fatal(err)
 50	}
 51	if n != 0 {
 52		t.Fatalf("%d tables remain after down-migration to 0", n)
 53	}
 54	if err := s.MigrateUp(); err != nil {
 55		t.Fatal(err)
 56	}
 57	// Idempotent at latest.
 58	if err := s.MigrateUp(); err != nil {
 59		t.Fatal(err)
 60	}
 61}
 62
 63func TestKeyFingerprintGloballyUnique(t *testing.T) {
 64	s := open(t)
 65	if err := s.MigrateUp(); err != nil {
 66		t.Fatal(err)
 67	}
 68	mustExec := func(q string, args ...any) {
 69		t.Helper()
 70		if _, err := s.DB.Exec(q, args...); err != nil {
 71			t.Fatal(err)
 72		}
 73	}
 74	mustExec("INSERT INTO users (username) VALUES ('alice'), ('bob')")
 75	mustExec("INSERT INTO ssh_keys (user_id, fingerprint, algo, blob) VALUES (1, 'SHA256:aaa', 'ed25519', x'00')")
 76
 77	// Same fingerprint on a different account must be rejected.
 78	_, err := s.DB.Exec("INSERT INTO ssh_keys (user_id, fingerprint, algo, blob) VALUES (2, 'SHA256:aaa', 'ed25519', x'00')")
 79	if err == nil || !strings.Contains(err.Error(), "UNIQUE") {
 80		t.Fatalf("duplicate ssh fingerprint across accounts: err = %v, want UNIQUE violation", err)
 81	}
 82
 83	mustExec("INSERT INTO pgp_keys (user_id, fingerprint, armored) VALUES (1, 'FPR1', '-----')")
 84	_, err = s.DB.Exec("INSERT INTO pgp_keys (user_id, fingerprint, armored) VALUES (2, 'FPR1', '-----')")
 85	if err == nil || !strings.Contains(err.Error(), "UNIQUE") {
 86		t.Fatalf("duplicate pgp fingerprint across accounts: err = %v, want UNIQUE violation", err)
 87	}
 88}
 89
 90func TestForeignKeysEnforced(t *testing.T) {
 91	s := open(t)
 92	if err := s.MigrateUp(); err != nil {
 93		t.Fatal(err)
 94	}
 95	_, err := s.DB.Exec("INSERT INTO ssh_keys (user_id, fingerprint, algo, blob) VALUES (999, 'SHA256:zzz', 'ed25519', x'00')")
 96	if err == nil {
 97		t.Fatal("insert with dangling user_id succeeded; foreign keys are off")
 98	}
 99}
100
101// The database file carries token hashes, addresses and private repo names.
102// The directory above it is the real boundary; this is the second one.
103func TestDatabaseFileIsNotWorldReadable(t *testing.T) {
104	path := filepath.Join(t.TempDir(), "gitbay.db")
105	s, err := Open(path)
106	if err != nil {
107		t.Fatal(err)
108	}
109	defer s.Close()
110	fi, err := os.Stat(path)
111	if err != nil {
112		t.Fatal(err)
113	}
114	if mode := fi.Mode().Perm(); mode&0o007 != 0 {
115		t.Errorf("database mode %04o is other-readable", mode)
116	}
117}