Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions internal/users/db/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,55 @@ func TestDeleteUser(t *testing.T) {
}
}

// TestBackwardCompatibilityAndMigrations covers loading legacy schemas (e.g., v2 with INT ugid)
// and migrating older schemas (e.g., v1 without 'locked' column) to the latest schema.
func TestBackwardCompatibilityAndMigrations(t *testing.T) {
t.Parallel()

tests := map[string]struct {
dump string
}{
"SchemaV2_IntUGID": {dump: filepath.Join("testdata", "TestLoadSchemaV2WithIntUGID", "one_user_and_group_v2.sql")},
"SchemaV1_NoLockedColumn": {dump: filepath.Join("testdata", "TestMigrationAddLockedColumnToUsersTable", "one_user_and_group_without_locked_column.sql")},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()

tempDir := t.TempDir()

err := db.Z_ForTests_CreateDBFromDump(tc.dump, tempDir)
require.NoError(t, err, "Setup: could not create database from dump")

// Open using current manager, it'll trigger a migration depending on schema_version.
m, err := db.New(tempDir)
require.NoError(t, err, "Setup: could not open manager for database")
t.Cleanup(func() { _ = m.Close() })

// Validate user can be read and that locked is false (either set or default).
u, err := m.UserByID(1111)
require.NoError(t, err, "Should read user from DB")
require.Equal(t, "user1", u.Name)
require.EqualValues(t, 11111, u.GID)
require.False(t, u.Locked, "locked should be false in both old and migrated schemas")

// Validate group and members. ugid should read as string regardless of underlying type.
g, err := m.GroupWithMembersByID(11111)
require.NoError(t, err, "Should read group from DB")
require.Equal(t, "group1", g.Name)
require.Equal(t, "12345678", g.UGID)
require.Len(t, g.Users, 1)
require.Equal(t, "user1", g.Users[0])

// Also ensure lookup by UGID works with string input.
gByUGID, err := m.GroupByUGID("12345678")
require.NoError(t, err)
require.EqualValues(t, 11111, gByUGID.GID)
})
}
}

// initDB returns a new database ready to be used alongside its database directory.
func initDB(t *testing.T, dbFile string) *db.Manager {
t.Helper()
Expand Down
12 changes: 6 additions & 6 deletions internal/users/db/sql/create_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ CREATE TABLE IF NOT EXISTS users (
dir TEXT DEFAULT "",
shell TEXT DEFAULT "/bin/bash",
broker_id TEXT DEFAULT "",
locked BOOLEAN DEFAULT FALSE
locked BOOLEAN DEFAULT FALSE
);
CREATE UNIQUE INDEX "idx_user_name" ON users ("name");

CREATE TABLE IF NOT EXISTS GROUPS (
CREATE TABLE IF NOT EXISTS groups (
name TEXT NOT NULL, -- Uniqueness is enforced by the index below
gid INT PRIMARY KEY, -- Uniqueness and not NULL is enforced by PRIMARY KEY
ugid INT NOT NULL -- Uniqueness is enforced by the index below
ugid TEXT NOT NULL -- Uniqueness is enforced by the index below
);
CREATE UNIQUE INDEX "idx_group_name" ON GROUPS ("name");
CREATE UNIQUE INDEX "idx_group_ugid" ON GROUPS ("ugid");
CREATE UNIQUE INDEX "idx_group_name" ON groups ("name");
CREATE UNIQUE INDEX "idx_group_ugid" ON groups ("ugid");

CREATE TABLE IF NOT EXISTS users_to_groups (
uid INT NOT NULL,
gid INT NOT NULL,
PRIMARY KEY (uid, gid),
FOREIGN KEY (uid) REFERENCES users (uid) ON DELETE CASCADE,
FOREIGN KEY (gid) REFERENCES GROUPS (gid) ON DELETE CASCADE
FOREIGN KEY (gid) REFERENCES groups (gid) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS users_to_local_groups (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS users (
name TEXT NOT NULL,
uid INT PRIMARY KEY,
gid INT NOT NULL,
gecos TEXT DEFAULT "",
dir TEXT DEFAULT "",
shell TEXT DEFAULT "/bin/bash",
broker_id TEXT DEFAULT "",
locked BOOLEAN DEFAULT FALSE
);
CREATE UNIQUE INDEX "idx_user_name" ON users ("name");

CREATE TABLE IF NOT EXISTS GROUPS (
name TEXT NOT NULL,
gid INT PRIMARY KEY,
ugid INT NOT NULL
);
CREATE UNIQUE INDEX "idx_group_name" ON GROUPS ("name");
CREATE UNIQUE INDEX "idx_group_ugid" ON GROUPS ("ugid");

CREATE TABLE IF NOT EXISTS users_to_groups (
uid INT NOT NULL,
gid INT NOT NULL,
PRIMARY KEY (uid, gid),
FOREIGN KEY (uid) REFERENCES users (uid) ON DELETE CASCADE,
FOREIGN KEY (gid) REFERENCES GROUPS (gid) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS users_to_local_groups (
uid INT NOT NULL,
group_name TEXT NOT NULL,
PRIMARY KEY (uid, group_name),
FOREIGN KEY (uid) REFERENCES users (uid) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS schema_version (
version INT PRIMARY KEY
);

-- Seed data using the old schema (ugid as INT)
INSERT INTO users (name, uid, gid, gecos, dir, shell, broker_id, locked)
VALUES ('user1', 1111, 11111, 'User1 gecos', '/home/user1', '/bin/bash', 'broker-id', FALSE);

INSERT INTO GROUPS (name, gid, ugid)
VALUES ('group1', 11111, 12345678);

INSERT INTO users_to_groups (uid, gid)
VALUES (1111, 11111);

-- Old schema v2
INSERT INTO schema_version VALUES (2);
COMMIT;
Loading