package blob import ( "bytes" "context" "errors" "io/fs" "os" "path/filepath" "testing" "cc.tiredsysadmin.keeporg/internal/secret" "cc.tiredsysadmin.keeporg/internal/sqldb" "cc.tiredsysadmin.keeporg/internal/sqldb/sqldbtest" ) func open(t *testing.T) (*Store, *sqldb.DB) { t.Helper() db, err := sqldb.Open(context.Background(), sqldbtest.URL(t)) if err != nil { t.Fatal(err) } t.Cleanup(func() { db.Close() }) k, _ := secret.NewKeyring(bytes.Repeat([]byte{3}, 32)) return Open(db, k), db } func TestPutGetIsIdempotentAndVersioned(t *testing.T) { s, _ := open(t) if err := s.Put("db", 1, []byte("v1")); err != nil { t.Fatal(err) } if err := s.Put("db", 1, []byte("v1")); err != nil { t.Fatal(err) } if err := s.Put("db", 2, []byte("v2")); err != nil { t.Fatal(err) } got, err := s.Get("db", 1) if err != nil || string(got) != "v1" { t.Fatalf("get v1 = %q, %v", got, err) } if vs := s.Versions("db"); len(vs) != 2 || vs[0] != 1 || vs[1] != 2 { t.Fatalf("versions = %v", vs) } if _, err := s.Get("db", 9); !errors.Is(err, fs.ErrNotExist) { t.Fatalf("missing version: %v", err) } } func TestPayloadsAreSealedAndBoundToTheirVersion(t *testing.T) { s, db := open(t) _ = s.Put("db", 1, []byte("KDBX-HEADER-one")) _ = s.Put("db", 2, []byte("KDBX-HEADER-two")) var body []byte if err := db.QueryRow(`SELECT body FROM keeporg_blobs WHERE version = 1`).Scan(&body); err != nil { t.Fatal(err) } if bytes.Contains(body, []byte("KDBX")) { t.Fatal("a payload is stored in the clear") } // Moving version 1's row over version 2 must not serve it as version 2. if _, err := db.Exec(db.Q(`UPDATE keeporg_blobs SET body = ? WHERE version = 2`), body); err != nil { t.Fatal(err) } if _, err := s.Get("db", 2); err == nil { t.Fatal("a payload opened under another version's name") } } func TestPruneKeepsNewest(t *testing.T) { s, _ := open(t) for i := int64(1); i <= KeepVersions+5; i++ { if err := s.Put("db", i, []byte{byte(i)}); err != nil { t.Fatal(err) } } _ = s.Put("other", 1, []byte("x")) if err := s.Prune("db"); err != nil { t.Fatal(err) } vs := s.Versions("db") if len(vs) != KeepVersions || vs[0] != 6 { t.Fatalf("after prune = %v", vs) } if err := s.Forget("db"); err != nil || len(s.Versions("db")) != 0 || len(s.Versions("other")) != 1 { t.Fatalf("forget: %v, left %v / %v", err, s.Versions("db"), s.Versions("other")) } } func TestImportDirReadsTheOldLayout(t *testing.T) { s, _ := open(t) dir := t.TempDir() os.MkdirAll(filepath.Join(dir, "abc"), 0o700) os.WriteFile(filepath.Join(dir, "abc", "00000000000000000003.kdbx"), []byte("three"), 0o600) os.WriteFile(filepath.Join(dir, "abc", "00000000000000000004.kdbx.tmp"), []byte("junk"), 0o600) n, err := s.ImportDir(dir) if err != nil || n != 1 { t.Fatalf("import = %d, %v", n, err) } if got, err := s.Get("abc", 3); err != nil || string(got) != "three" { t.Fatalf("imported = %q, %v", got, err) } }