mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-08-20 11:56:23 +00:00
208 lines
7.5 KiB
Go
208 lines
7.5 KiB
Go
package usersignup
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
|
testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing"
|
|
)
|
|
|
|
// versionBeforeMoveTokens is the migration version right before the "actor tokens" migration.
|
|
const versionBeforeMoveTokens = 20260722120000
|
|
|
|
// seedSignupTokensForMigration seeds two signup tokens (one with a user group, one without) into the pre-migration schema.
|
|
func seedSignupTokensForMigration(t *testing.T, db *gorm.DB, createdAt, expiresAt time.Time) {
|
|
t.Helper()
|
|
|
|
// An unrelated, non-JSON kv entry, to ensure the freeze/restore queries don't choke on other kv keys
|
|
err := db.Exec(
|
|
`INSERT INTO kv ("key", "value") VALUES ('instance_id', ?)`,
|
|
"not-json-instance-id",
|
|
).Error
|
|
require.NoError(t, err)
|
|
|
|
// A user group referenced by one of the tokens
|
|
err = db.Exec(
|
|
`INSERT INTO user_groups (id, created_at, friendly_name, name) VALUES (?, ?, ?, ?)`,
|
|
"grp-1", createdAt.Unix(), "Group One", "group-one",
|
|
).Error
|
|
require.NoError(t, err)
|
|
|
|
// A token with a user group
|
|
err = db.Exec(
|
|
`INSERT INTO signup_tokens (id, created_at, token, expires_at, usage_limit, usage_count) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
"tok-1", createdAt.Unix(), "TOKENWITHGROUP01", expiresAt.Unix(), 3, 1,
|
|
).Error
|
|
require.NoError(t, err)
|
|
err = db.Exec(
|
|
`INSERT INTO signup_tokens_user_groups (signup_token_id, user_group_id) VALUES (?, ?)`,
|
|
"tok-1", "grp-1",
|
|
).Error
|
|
require.NoError(t, err)
|
|
|
|
// A token without user groups
|
|
err = db.Exec(
|
|
`INSERT INTO signup_tokens (id, created_at, token, expires_at, usage_limit, usage_count) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
"tok-2", createdAt.Unix(), "TOKENNOGROUP0002", expiresAt.Unix(), 1, 0,
|
|
).Error
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestLoadMigratedSignupTokens(t *testing.T) {
|
|
createdAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
|
expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second)
|
|
|
|
db := testutils.NewDatabaseForTestWithMigrationSeed(t, versionBeforeMoveTokens, func(t *testing.T, db *gorm.DB) {
|
|
seedSignupTokensForMigration(t, db, createdAt, expiresAt)
|
|
})
|
|
|
|
// The migration must have dropped the signup_tokens tables
|
|
ok := db.Migrator().HasTable("signup_tokens")
|
|
require.False(t, ok, "signup_tokens table should have been dropped")
|
|
ok = db.Migrator().HasTable("signup_tokens_user_groups")
|
|
require.False(t, ok, "signup_tokens_user_groups table should have been dropped")
|
|
|
|
tokens, err := loadMigratedSignupTokens(t.Context(), db)
|
|
require.NoError(t, err)
|
|
require.Len(t, tokens, 2)
|
|
|
|
byID := make(map[string]migratedSignupToken, len(tokens))
|
|
for _, tok := range tokens {
|
|
byID[tok.ID] = tok
|
|
}
|
|
|
|
tok1 := byID["tok-1"]
|
|
require.Equal(t, "TOKENWITHGROUP01", tok1.Token)
|
|
require.Equal(t, 3, tok1.UsageLimit)
|
|
require.Equal(t, 1, tok1.UsageCount)
|
|
require.Equal(t, []string{"grp-1"}, tok1.UserGroupIDs)
|
|
require.Equal(t, expiresAt.Unix(), tok1.ExpiresAt)
|
|
require.Equal(t, createdAt.Unix(), tok1.CreatedAt)
|
|
|
|
tok2 := byID["tok-2"]
|
|
require.Equal(t, "TOKENNOGROUP0002", tok2.Token)
|
|
require.Equal(t, 1, tok2.UsageLimit)
|
|
require.Equal(t, 0, tok2.UsageCount)
|
|
require.Empty(t, tok2.UserGroupIDs)
|
|
}
|
|
|
|
// TestMigrateSignupTokens verifies that the frozen signup tokens are turned into per-token actors, and that already-expired ones are skipped
|
|
func TestMigrateSignupTokens(t *testing.T) {
|
|
createdAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
|
expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second)
|
|
|
|
db := testutils.NewDatabaseForTestWithMigrationSeed(t, versionBeforeMoveTokens, func(t *testing.T, db *gorm.DB) {
|
|
seedSignupTokensForMigration(t, db, createdAt, expiresAt)
|
|
|
|
// A token that has already expired: it must not be migrated
|
|
err := db.Exec(
|
|
`INSERT INTO signup_tokens (id, created_at, token, expires_at, usage_limit, usage_count) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
"tok-expired", createdAt.Unix(), "EXPIREDTOKEN0003", time.Now().Add(-time.Hour).Unix(), 1, 0,
|
|
).Error
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
svc := newSignupServiceForTest(t, db, fakeUserCreator{})
|
|
|
|
err := svc.migrateSignupTokens(t.Context())
|
|
require.NoError(t, err)
|
|
|
|
entries, err := svc.listSignupTokenStates(t.Context())
|
|
require.NoError(t, err)
|
|
require.Len(t, entries, 2)
|
|
|
|
byToken := make(map[string]SignupTokenState, len(entries))
|
|
for _, e := range entries {
|
|
byToken[e.Token] = e.State
|
|
}
|
|
|
|
tok1 := byToken["TOKENWITHGROUP01"]
|
|
require.Equal(t, "tok-1", tok1.ID)
|
|
require.Equal(t, 3, tok1.UsageLimit)
|
|
require.Equal(t, 1, tok1.UsageCount)
|
|
require.Equal(t, []string{"grp-1"}, tok1.UserGroupIDs)
|
|
require.Equal(t, expiresAt.Unix(), tok1.ExpiresAt.Unix())
|
|
require.Equal(t, createdAt.Unix(), tok1.CreatedAt.Unix())
|
|
|
|
tok2 := byToken["TOKENNOGROUP0002"]
|
|
require.Equal(t, "tok-2", tok2.ID)
|
|
require.Empty(t, tok2.UserGroupIDs)
|
|
|
|
// The expired token must not have been migrated
|
|
require.NotContains(t, byToken, "EXPIREDTOKEN0003")
|
|
|
|
// The migration is idempotent: re-running it doesn't reset a token that has been used since
|
|
require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc.actorService, "TOKENNOGROUP0002").Status)
|
|
err = svc.migrateSignupTokens(t.Context())
|
|
require.NoError(t, err)
|
|
require.Equal(t, signupTokenConsumeLimitReached, consumeSignupTokenForTest(t, svc.actorService, "TOKENNOGROUP0002").Status)
|
|
}
|
|
|
|
// TestLoadMigratedSignupTokensEmpty verifies that when there were no signup tokens, nothing is frozen and nothing is loaded.
|
|
func TestLoadMigratedSignupTokensEmpty(t *testing.T) {
|
|
db := testutils.NewDatabaseForTest(t)
|
|
|
|
tokens, err := loadMigratedSignupTokens(t.Context(), db)
|
|
require.NoError(t, err)
|
|
require.Empty(t, tokens)
|
|
}
|
|
|
|
// TestMoveTokensToActorStateDown verifies that rolling the migration back recreates the signup token tables and restores their contents from the frozen kv document.
|
|
func TestMoveTokensToActorStateDown(t *testing.T) {
|
|
createdAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
|
expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second)
|
|
|
|
db := testutils.NewDatabaseForTestWithMigrationSeed(t, versionBeforeMoveTokens, func(t *testing.T, db *gorm.DB) {
|
|
seedSignupTokensForMigration(t, db, createdAt, expiresAt)
|
|
})
|
|
|
|
// The tables were frozen and dropped by the up migration
|
|
ok := db.Migrator().HasTable("signup_tokens")
|
|
require.False(t, ok)
|
|
|
|
// Roll the migration back
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
m, cleanup, err := utils.GetEmbeddedMigrateInstance(t.Context(), sqlDB)
|
|
require.NoError(t, err)
|
|
defer cleanup()
|
|
|
|
err = m.Migrate(versionBeforeMoveTokens)
|
|
require.NoError(t, err)
|
|
|
|
// The tables must have been recreated and repopulated from the frozen document
|
|
ok = db.Migrator().HasTable("signup_tokens")
|
|
require.True(t, ok)
|
|
ok = db.Migrator().HasTable("signup_tokens_user_groups")
|
|
require.True(t, ok)
|
|
|
|
type row struct {
|
|
ID string
|
|
Token string
|
|
UsageLimit int
|
|
UsageCount int
|
|
}
|
|
var rows []row
|
|
err = db.Raw(`SELECT id, token, usage_limit, usage_count FROM signup_tokens ORDER BY id`).Scan(&rows).Error
|
|
require.NoError(t, err)
|
|
require.Equal(t, []row{
|
|
{ID: "tok-1", Token: "TOKENWITHGROUP01", UsageLimit: 3, UsageCount: 1},
|
|
{ID: "tok-2", Token: "TOKENNOGROUP0002", UsageLimit: 1, UsageCount: 0},
|
|
}, rows)
|
|
|
|
var groupID string
|
|
err = db.Raw(`SELECT user_group_id FROM signup_tokens_user_groups WHERE signup_token_id = ?`, "tok-1").Scan(&groupID).Error
|
|
require.NoError(t, err)
|
|
require.Equal(t, "grp-1", groupID)
|
|
|
|
// The frozen document must have been removed from the kv table
|
|
var kvCount int64
|
|
err = db.Raw(`SELECT count(*) FROM kv WHERE "key" = ?`, signupTokensMigratedKey).Scan(&kvCount).Error
|
|
require.NoError(t, err)
|
|
require.Zero(t, kvCount)
|
|
}
|