Rename the active product, Go module, binaries, configuration, protocols and documentation to Piik while retaining the TV mascot and existing media/room behavior. Use one current private contract without old-name aliases. Preserve SQLite authority and copy compatible local settings once; other settings start fresh. Git-message cleanup and the infrastructure name cutover are coordinated with this phase, with original history and deployment data archived externally. Validated locally with Web and Go checks, Windows capture compilation, matched Client/Server builds, packaged Client control handshake and history-rewrite integrity checks. Skip duplicate hosted CI for this owner-authorized local release. [skip ci]
594 lines
19 KiB
Go
594 lines
19 KiB
Go
package room
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"database/sql"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/TNTcraftHIM/Piik/internal/server/protocol"
|
|
)
|
|
|
|
// The scenarios below are ported from tests/room-database.test.ts.
|
|
|
|
func databasePath(t *testing.T) string {
|
|
t.Helper()
|
|
// t.TempDir registers its removal before any store cleanup below, so the
|
|
// database is always closed first; Windows will not remove an open file.
|
|
return filepath.Join(t.TempDir(), "rooms.sqlite")
|
|
}
|
|
|
|
func stableStore(t *testing.T, path string, options Options) *Store {
|
|
t.Helper()
|
|
database, err := NewDatabase(path)
|
|
if err != nil {
|
|
t.Fatalf("NewDatabase: %v", err)
|
|
}
|
|
if options.MaxRooms == 0 {
|
|
options.MaxRooms = 8
|
|
}
|
|
options.MaxViewersPerRoom = 8
|
|
options.Database = database
|
|
store, err := New(options)
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = store.Close() })
|
|
if err := store.Initialize(); err != nil {
|
|
t.Fatalf("Initialize: %v", err)
|
|
}
|
|
return store
|
|
}
|
|
|
|
func rawDatabase(t *testing.T, path string) *sql.DB {
|
|
t.Helper()
|
|
database, err := sql.Open("sqlite", "file:"+filepath.ToSlash(path))
|
|
if err != nil {
|
|
t.Fatalf("sql.Open: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = database.Close() })
|
|
return database
|
|
}
|
|
|
|
func rawExec(t *testing.T, path, statement string) {
|
|
t.Helper()
|
|
if _, err := rawDatabase(t, path).Exec(statement); err != nil {
|
|
t.Fatalf("raw exec %q: %v", statement, err)
|
|
}
|
|
}
|
|
|
|
func dbViewerInput(roomID, sessionID, viewerGrant string) ConnectParticipantInput {
|
|
input := viewerInput(roomID, sessionID, viewerGrant)
|
|
input.ClientID = "client-" + sessionID
|
|
return input
|
|
}
|
|
|
|
func TestDoesNotPersistARoomWhenPasswordDerivationIsBusy(t *testing.T) {
|
|
path := databasePath(t)
|
|
first := stableStore(t, path, Options{MaxRooms: 1})
|
|
|
|
withSaturatedGate(t, first, func() {
|
|
_, err := tryCreateRoom(first, protocol.CodeEntryPrivate, "room-password", "4321")
|
|
expectCode(t, err, CodeRoomBusy)
|
|
})
|
|
if first.Size() != 0 {
|
|
t.Fatalf("size = %d, want 0", first.Size())
|
|
}
|
|
if err := first.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
second := stableStore(t, path, Options{MaxRooms: 1})
|
|
if second.Size() != 0 {
|
|
t.Fatalf("restored size = %d, want 0", second.Size())
|
|
}
|
|
if created := createRoom(t, second, protocol.CodeEntryPrivate, "", "4321"); created.RoomID != "4321" {
|
|
t.Fatalf("room = %q, want 4321", created.RoomID)
|
|
}
|
|
}
|
|
|
|
func TestRestoresTheExactAuthorityAggregateWithoutParticipants(t *testing.T) {
|
|
path := databasePath(t)
|
|
first := stableStore(t, path, Options{})
|
|
room := createRoom(t, first, protocol.CodeEntryOpen, "", "4321")
|
|
if _, err := first.SetCodeEntryPolicy(
|
|
room.RoomID, protocol.CodeEntryPrivate, room.HostToken); err != nil {
|
|
t.Fatalf("SetCodeEntryPolicy: %v", err)
|
|
}
|
|
if _, err := trySetViewerPassword(
|
|
first, room.RoomID, "room-password", room.HostToken); err != nil {
|
|
t.Fatalf("SetViewerPassword: %v", err)
|
|
}
|
|
rotated, err := first.SetViewerGrant(room.RoomID, "rotate", room.HostToken)
|
|
if err != nil {
|
|
t.Fatalf("SetViewerGrant: %v", err)
|
|
}
|
|
host := mustConnect(t, first, hostInput(room.RoomID, room.HostToken))
|
|
oldViewer := mustConnect(t, first, dbViewerInput(room.RoomID, "old-viewer", rotated.ViewerGrant))
|
|
if connected, ok := first.GetConnectedHost(room.RoomID); !ok || connected.PeerID != host.PeerID {
|
|
t.Fatalf("host = %+v %v", connected, ok)
|
|
}
|
|
if _, ok := first.GetConnectedViewer(room.RoomID, oldViewer.PeerID); !ok {
|
|
t.Fatal("viewer is not connected")
|
|
}
|
|
if err := first.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
second := stableStore(t, path, Options{})
|
|
if second.Size() != 1 {
|
|
t.Fatalf("restored size = %d, want 1", second.Size())
|
|
}
|
|
if _, ok := second.GetConnectedHost(room.RoomID); ok {
|
|
t.Fatal("a restored room reported a connected host")
|
|
}
|
|
if viewers := second.GetConnectedViewers(room.RoomID); len(viewers) != 0 {
|
|
t.Fatalf("restored viewers = %+v", viewers)
|
|
}
|
|
_, err = second.ConnectParticipant(dbViewerInput(room.RoomID, "revoked-grant", room.ViewerGrant))
|
|
expectCode(t, err, CodeInvalidToken)
|
|
|
|
granted := mustConnect(t, second, dbViewerInput(room.RoomID, "current-grant", rotated.ViewerGrant))
|
|
if granted.ViewerAuthorizationGeneration != rotated.ViewerAuthorizationGeneration {
|
|
t.Fatalf("generation = %q, want %q",
|
|
granted.ViewerAuthorizationGeneration, rotated.ViewerAuthorizationGeneration)
|
|
}
|
|
connected, err := tryConnectViewerWithPassword(second, ConnectViewerWithPasswordInput{
|
|
RoomID: room.RoomID,
|
|
Password: "room-password",
|
|
ClientID: "password-client",
|
|
SessionID: "password-session",
|
|
}, nil)
|
|
if err != nil || connected.CodeEntryPolicy != protocol.CodeEntryPrivate {
|
|
t.Fatalf("password login = %+v, %v", connected, err)
|
|
}
|
|
_, err = second.ConnectParticipant(hostInput(room.RoomID, "wrong-token"))
|
|
expectCode(t, err, CodeInvalidToken)
|
|
mustConnect(t, second, hostInput(room.RoomID, room.HostToken, "recovered-host"))
|
|
if err := second.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
raw := rawDatabase(t, path)
|
|
rows, err := raw.Query("SELECT * FROM rooms")
|
|
if err != nil {
|
|
t.Fatalf("raw query: %v", err)
|
|
}
|
|
defer rows.Close()
|
|
columns, err := rows.Columns()
|
|
if err != nil {
|
|
t.Fatalf("Columns: %v", err)
|
|
}
|
|
want := []string{
|
|
"room_id", "host_token_digest", "viewer_grant_digest",
|
|
"viewer_authorization_generation", "code_entry_policy",
|
|
"viewer_password_material",
|
|
}
|
|
if strings.Join(columns, ",") != strings.Join(want, ",") {
|
|
t.Fatalf("columns = %v", columns)
|
|
}
|
|
if !rows.Next() {
|
|
t.Fatal("no stored room")
|
|
}
|
|
var (
|
|
roomID string
|
|
hostDigest []byte
|
|
grantDigest []byte
|
|
generation string
|
|
policy string
|
|
material []byte
|
|
)
|
|
if err := rows.Scan(&roomID, &hostDigest, &grantDigest, &generation, &policy,
|
|
&material); err != nil {
|
|
t.Fatalf("Scan: %v", err)
|
|
}
|
|
hostTokenDigest := sha256.Sum256([]byte(room.HostToken))
|
|
if !bytes.Equal(hostDigest, hostTokenDigest[:]) {
|
|
t.Fatal("stored Host token digest does not match")
|
|
}
|
|
grantTokenDigest := sha256.Sum256([]byte(rotated.ViewerGrant))
|
|
if !bytes.Equal(grantDigest, grantTokenDigest[:]) {
|
|
t.Fatal("stored Viewer grant digest does not match")
|
|
}
|
|
if len(material) != 48 {
|
|
t.Fatalf("password material = %d bytes, want 48", len(material))
|
|
}
|
|
// Nothing reversible is stored: no token, grant or password appears.
|
|
stored := roomID + generation + policy + string(hostDigest) + string(grantDigest) + string(material)
|
|
for _, secret := range []string{room.HostToken, rotated.ViewerGrant, "room-password"} {
|
|
if strings.Contains(stored, secret) {
|
|
t.Fatal("a credential was stored verbatim")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPreservesDisconnectedAuthorityAcrossRepeatedRestarts(t *testing.T) {
|
|
path := databasePath(t)
|
|
first := stableStore(t, path, Options{})
|
|
room := createRoom(t, first, protocol.CodeEntryOpen, "", "5890")
|
|
host := mustConnect(t, first, hostInput(room.RoomID, room.HostToken))
|
|
if _, err := first.DisconnectParticipant(room.RoomID, host.PeerID, "host-session"); err != nil {
|
|
t.Fatalf("DisconnectParticipant: %v", err)
|
|
}
|
|
if err := first.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
for range 3 {
|
|
restored := stableStore(t, path, Options{})
|
|
if restored.Size() != 1 {
|
|
t.Fatalf("size = %d, want 1", restored.Size())
|
|
}
|
|
viewer := mustConnect(t, restored, dbViewerInput(room.RoomID, "dormant-viewer", room.ViewerGrant))
|
|
if viewer.HostOnline {
|
|
t.Fatal("restored room reported an online host")
|
|
}
|
|
mustConnect(t, restored, hostInput(room.RoomID, room.HostToken))
|
|
if err := restored.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPersistsExplicitRoomDeletionBeforeRecyclingItsCode(t *testing.T) {
|
|
path := databasePath(t)
|
|
first := stableStore(t, path, Options{})
|
|
room := createRoom(t, first, protocol.CodeEntryOpen, "", "6789")
|
|
closed, err := first.AbandonRoom(room.RoomID)
|
|
if err != nil || closed == nil || closed.RoomID != room.RoomID {
|
|
t.Fatalf("AbandonRoom = %v, %v", closed, err)
|
|
}
|
|
if err := first.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
second := stableStore(t, path, Options{})
|
|
if second.Size() != 0 {
|
|
t.Fatalf("size = %d, want 0", second.Size())
|
|
}
|
|
replacement := createRoom(t, second, protocol.CodeEntryOpen, "", room.RoomID)
|
|
if replacement.RoomID != room.RoomID {
|
|
t.Fatalf("replacement room = %q", replacement.RoomID)
|
|
}
|
|
if replacement.HostToken == room.HostToken || replacement.ViewerGrant == room.ViewerGrant {
|
|
t.Fatal("the recycled code kept the old credentials")
|
|
}
|
|
}
|
|
|
|
func TestPersistsRoomReplacementAsOneAuthorityTransition(t *testing.T) {
|
|
path := databasePath(t)
|
|
first := stableStore(t, path, Options{})
|
|
original := createRoom(t, first, protocol.CodeEntryOpen, "", "4321")
|
|
replacement, err := tryReplaceRoom(
|
|
first, original.RoomID, original.HostToken, protocol.CodeEntryPrivate, "new-password")
|
|
if err != nil {
|
|
t.Fatalf("ReplaceRoom: %v", err)
|
|
}
|
|
if err := first.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
second := stableStore(t, path, Options{})
|
|
if second.Size() != 1 {
|
|
t.Fatalf("size = %d, want 1", second.Size())
|
|
}
|
|
_, err = second.ConnectParticipant(hostInput(original.RoomID, original.HostToken))
|
|
expectCode(t, err, CodeInvalidToken)
|
|
connected := mustConnect(t, second, dbViewerInput(
|
|
replacement.Created.RoomID, "replacement-viewer", replacement.Created.ViewerGrant))
|
|
if connected.RoomID != replacement.Created.RoomID {
|
|
t.Fatalf("viewer joined %q", connected.RoomID)
|
|
}
|
|
withPassword, err := tryConnectViewerWithPassword(second, ConnectViewerWithPasswordInput{
|
|
RoomID: replacement.Created.RoomID,
|
|
Password: "new-password",
|
|
ClientID: "password-viewer",
|
|
SessionID: "password-session",
|
|
}, nil)
|
|
if err != nil || withPassword.RoomID != replacement.Created.RoomID {
|
|
t.Fatalf("password login = %+v, %v", withPassword, err)
|
|
}
|
|
}
|
|
|
|
func TestRollsBackReplacementWhenOldAuthorityIsStale(t *testing.T) {
|
|
path := databasePath(t)
|
|
first := stableStore(t, path, Options{})
|
|
createRoom(t, first, protocol.CodeEntryPrivate, "room-password", "4321")
|
|
if err := first.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
database, err := NewDatabase(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = database.Close() })
|
|
before, err := database.Initialize()
|
|
if err != nil || len(before) != 1 {
|
|
t.Fatalf("Initialize = %v, %v", before, err)
|
|
}
|
|
replacement := before[0]
|
|
replacement.RoomID = "5678"
|
|
err = database.ReplaceRoom("4321", make([]byte, 32), replacement)
|
|
if err == nil || err.Error() != "Room database replacement delete did not match current authority" {
|
|
t.Fatalf("ReplaceRoom = %v", err)
|
|
}
|
|
if err := database.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
after, err := database.Initialize()
|
|
if err != nil || !reflect.DeepEqual(after, before) {
|
|
t.Fatalf("failed replacement changed durable authority: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestOpensThePathWithURIReservedCharactersLiterally: `new DatabaseSync(path)`
|
|
// opened the literal path, while the driver's DSN is a URI in which `%41`
|
|
// decodes to `A` and `#` ends the file name; the file must still be the
|
|
// configured one, and the busy-timeout pragma must survive the escaping.
|
|
func TestOpensThePathWithURIReservedCharactersLiterally(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "rooms%41#1.sqlite")
|
|
first := stableStore(t, path, Options{})
|
|
createRoom(t, first, protocol.CodeEntryOpen, "", "4321")
|
|
if err := first.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Fatalf("the configured file was not the one opened: %v", err)
|
|
}
|
|
if second := stableStore(t, path, Options{}); second.Size() != 1 {
|
|
t.Fatalf("restored size = %d, want 1", second.Size())
|
|
}
|
|
}
|
|
|
|
func TestRejectsAnotherOwnerOfTheSameDatabase(t *testing.T) {
|
|
path := databasePath(t)
|
|
first, err := NewDatabase(path)
|
|
if err != nil {
|
|
t.Fatalf("NewDatabase: %v", err)
|
|
}
|
|
if _, err := first.Initialize(); err != nil {
|
|
t.Fatalf("Initialize: %v", err)
|
|
}
|
|
contender, err := NewDatabase(path)
|
|
if err != nil {
|
|
t.Fatalf("NewDatabase: %v", err)
|
|
}
|
|
_, err = contender.Initialize()
|
|
if err == nil || !strings.Contains(strings.ToLower(err.Error()), "locked") {
|
|
t.Fatalf("contender Initialize = %v, want a lock failure", err)
|
|
}
|
|
if err := contender.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
if err := first.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
successor, err := NewDatabase(path)
|
|
if err != nil {
|
|
t.Fatalf("NewDatabase: %v", err)
|
|
}
|
|
restored, err := successor.Initialize()
|
|
if err != nil || len(restored) != 0 {
|
|
t.Fatalf("successor Initialize = %v, %v", restored, err)
|
|
}
|
|
if err := successor.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRejectsAnUnknownDatabaseIdentity(t *testing.T) {
|
|
for _, testCase := range []struct{ name, mutation string }{
|
|
{"application identity", "PRAGMA application_id = 1"},
|
|
{"schema version", "PRAGMA user_version = 99"},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
path := databasePath(t)
|
|
initial, err := NewDatabase(path)
|
|
if err != nil {
|
|
t.Fatalf("NewDatabase: %v", err)
|
|
}
|
|
if _, err := initial.Initialize(); err != nil {
|
|
t.Fatalf("Initialize: %v", err)
|
|
}
|
|
if err := initial.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
rawExec(t, path, testCase.mutation)
|
|
|
|
reopened, err := NewDatabase(path)
|
|
if err != nil {
|
|
t.Fatalf("NewDatabase: %v", err)
|
|
}
|
|
_, err = reopened.Initialize()
|
|
if err == nil || !strings.Contains(err.Error(), "does not match") {
|
|
t.Fatalf("Initialize = %v", err)
|
|
}
|
|
if err := reopened.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRejectsAnAlteredCurrentVersionSchema(t *testing.T) {
|
|
path := databasePath(t)
|
|
initial, err := NewDatabase(path)
|
|
if err != nil {
|
|
t.Fatalf("NewDatabase: %v", err)
|
|
}
|
|
if _, err := initial.Initialize(); err != nil {
|
|
t.Fatalf("Initialize: %v", err)
|
|
}
|
|
if err := initial.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
rawExec(t, path, "ALTER TABLE rooms ADD COLUMN unexpected TEXT")
|
|
|
|
reopened, err := NewDatabase(path)
|
|
if err != nil {
|
|
t.Fatalf("NewDatabase: %v", err)
|
|
}
|
|
_, err = reopened.Initialize()
|
|
if err == nil || err.Error() != "Room database columns do not match the current schema" {
|
|
t.Fatalf("Initialize = %v", err)
|
|
}
|
|
if err := reopened.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRejectsCorruptAuthorityRows(t *testing.T) {
|
|
path := databasePath(t)
|
|
initial := stableStore(t, path, Options{})
|
|
createRoom(t, initial, protocol.CodeEntryOpen, "", "7890")
|
|
if err := initial.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
raw := rawDatabase(t, path)
|
|
if _, err := raw.Exec("PRAGMA ignore_check_constraints = ON"); err != nil {
|
|
t.Fatalf("raw pragma: %v", err)
|
|
}
|
|
if _, err := raw.Exec("UPDATE rooms SET host_token_digest = zeroblob(31)"); err != nil {
|
|
t.Fatalf("raw update: %v", err)
|
|
}
|
|
if err := raw.Close(); err != nil {
|
|
t.Fatalf("raw close: %v", err)
|
|
}
|
|
|
|
database, err := NewDatabase(path)
|
|
if err != nil {
|
|
t.Fatalf("NewDatabase: %v", err)
|
|
}
|
|
reopened, err := New(Options{
|
|
MaxRooms: 8,
|
|
MaxViewersPerRoom: 8,
|
|
Database: database,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
// The oracle accepts either rejection: this SQLite build reports the broken
|
|
// row from PRAGMA quick_check before the digest length assertion runs.
|
|
err = reopened.Initialize()
|
|
if err == nil || (!strings.Contains(err.Error(), "Host token") &&
|
|
!strings.Contains(err.Error(), "integrity")) {
|
|
t.Fatalf("Initialize = %v", err)
|
|
}
|
|
if err := reopened.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
}
|
|
|
|
// Invalid authority must be rejected before mutating SQLite.
|
|
func TestRejectsInvalidAuthorityArguments(t *testing.T) {
|
|
valid := StoredRoomAuthority{
|
|
RoomID: "1234",
|
|
HostTokenDigest: make([]byte, 32),
|
|
ViewerGrantDigest: make([]byte, 32),
|
|
ViewerAuthorizationGeneration: "MzMzMzMzMzMzMzMzMzMzMw",
|
|
CodeEntryPolicy: protocol.CodeEntryOpen,
|
|
}
|
|
shortDigest := make([]byte, 31)
|
|
for _, testCase := range []struct {
|
|
name string
|
|
run func(*Database) error
|
|
want string
|
|
}{
|
|
{"room ID", func(d *Database) error {
|
|
room := valid
|
|
room.RoomID = "0123"
|
|
return d.InsertRoom(room)
|
|
}, "Room database contains an invalid room ID"},
|
|
{"host digest", func(d *Database) error {
|
|
room := valid
|
|
room.HostTokenDigest = shortDigest
|
|
return d.InsertRoom(room)
|
|
}, "Host token digest must contain 32 bytes"},
|
|
{"grant digest", func(d *Database) error {
|
|
room := valid
|
|
room.ViewerGrantDigest = shortDigest
|
|
return d.InsertRoom(room)
|
|
}, "Viewer grant digest must contain 32 bytes"},
|
|
{"generation", func(d *Database) error {
|
|
room := valid
|
|
room.ViewerAuthorizationGeneration = "MzMzMzMzMzMzMzMzMzMzMw=="
|
|
return d.InsertRoom(room)
|
|
}, "Viewer authorization generation is invalid"},
|
|
{"policy", func(d *Database) error {
|
|
room := valid
|
|
room.CodeEntryPolicy = "public"
|
|
return d.InsertRoom(room)
|
|
}, "Room database contains an invalid code-entry policy"},
|
|
{"password material", func(d *Database) error {
|
|
room := valid
|
|
room.ViewerPasswordMaterial = make([]byte, 47)
|
|
return d.InsertRoom(room)
|
|
}, "Viewer password material must contain 48 bytes"},
|
|
{"identity", func(d *Database) error {
|
|
return d.DeleteRoom("123", make([]byte, 32))
|
|
}, "Room ID is invalid"},
|
|
{"update policy", func(d *Database) error {
|
|
return d.SetCodeEntryPolicy("1234", make([]byte, 32), "public")
|
|
}, "Room code-entry policy is invalid"},
|
|
{"replacement identity", func(d *Database) error {
|
|
return d.ReplaceRoom("1234", make([]byte, 32), valid)
|
|
}, "Replacement room ID must be different"},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
database, err := NewDatabase(databasePath(t))
|
|
if err != nil {
|
|
t.Fatalf("NewDatabase: %v", err)
|
|
}
|
|
if _, err := database.Initialize(); err != nil {
|
|
t.Fatalf("Initialize: %v", err)
|
|
}
|
|
defer func() { _ = database.Close() }()
|
|
if err := testCase.run(database); err == nil || err.Error() != testCase.want {
|
|
t.Fatalf("error = %v, want %q", err, testCase.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRejectsAMemoryDatabasePath(t *testing.T) {
|
|
for _, path := range []string{"", ":memory:"} {
|
|
if _, err := NewDatabase(path); err == nil ||
|
|
err.Error() != "Room database path must identify a file" {
|
|
t.Fatalf("NewDatabase(%q) = %v", path, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRejectsASecondInitialize(t *testing.T) {
|
|
path := databasePath(t)
|
|
database, err := NewDatabase(path)
|
|
if err != nil {
|
|
t.Fatalf("NewDatabase: %v", err)
|
|
}
|
|
if _, err := database.Initialize(); err != nil {
|
|
t.Fatalf("Initialize: %v", err)
|
|
}
|
|
if _, err := database.Initialize(); err == nil ||
|
|
err.Error() != "Room database is already initialized" {
|
|
t.Fatalf("second Initialize = %v", err)
|
|
}
|
|
if err := database.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
if err := database.InsertRoom(StoredRoomAuthority{
|
|
RoomID: "1234",
|
|
HostTokenDigest: make([]byte, 32),
|
|
ViewerGrantDigest: make([]byte, 32),
|
|
ViewerAuthorizationGeneration: "MzMzMzMzMzMzMzMzMzMzMw",
|
|
CodeEntryPolicy: protocol.CodeEntryOpen,
|
|
}); err == nil || err.Error() != "Room database is not initialized" {
|
|
t.Fatalf("InsertRoom after Close = %v", err)
|
|
}
|
|
}
|