owpengram-server/internal/store/postgres/authorization_tombstone_integration_test.go
2026-09-01 12:06:31 +03:00

89 lines
3.4 KiB
Go

package postgres
import (
"context"
"errors"
"testing"
"time"
"telesrv/internal/domain"
)
const tombstoneAuthorizationTestUserSQL = `
UPDATE users SET
phone = '', first_name = '', last_name = '', username = '', country_code = '', about = '',
verified = false, support = false, last_seen_at = 0,
premium_expires_at = NULL, emoji_status_document_id = 0, emoji_status_until = 0,
emoji_status_collectible_id = NULL, emoji_status_collectible = '{}'::jsonb,
color_set = false, color = 0, color_background_emoji_id = 0,
profile_color_set = false, profile_color = 0, profile_color_background_emoji_id = 0,
birthday_day = 0, birthday_month = 0, birthday_year = 0, personal_channel_id = 0,
deleted_at = $2, deletion_source = 'manual', deletion_reason = '',
account_delete_at = NULL, updated_at = $2
WHERE id = $1 AND deleted_at IS NULL`
func TestAuthorizationStoreBindRejectsTombstonePostgres(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
userID := createRevokeTestUser(t, ctx, pool, "bind-tombstone")
key := saveTempIdentityTestAuthKey(t, ctx, pool, NewAuthKeyStore(pool), 0)
if _, err := pool.Exec(ctx, tombstoneAuthorizationTestUserSQL, userID, time.Now().UTC()); err != nil {
t.Fatalf("tombstone user: %v", err)
}
err := NewAuthorizationStore(pool).Bind(ctx, domain.Authorization{AuthKeyID: key, UserID: userID})
if !errors.Is(err, domain.ErrAccountDeleted) {
t.Fatalf("Bind tombstone err = %v, want ErrAccountDeleted", err)
}
assertRevokeTestNoAuthorization(t, ctx, NewAuthorizationStore(pool), key)
assertRevokeTestTableCount(t, ctx, pool, "update_states", "auth_key_id", authKeyIDToInt64(key), 0)
}
func TestAuthorizationStoreBindWaitsForTombstoneThenRejectsPostgres(t *testing.T) {
pool := testPool(t)
testCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
t.Cleanup(cancel)
userID := createRevokeTestUser(t, testCtx, pool, "bind-tombstone-race")
key := saveTempIdentityTestAuthKey(t, testCtx, pool, NewAuthKeyStore(pool), 0)
deleteTx, err := pool.Begin(testCtx)
if err != nil {
t.Fatalf("begin tombstone transaction: %v", err)
}
defer func() { _ = deleteTx.Rollback(context.Background()) }()
if err := lockUsersForUpdate(testCtx, deleteTx, userID); err != nil {
t.Fatalf("lock tombstone user: %v", err)
}
if _, err := deleteTx.Exec(testCtx, tombstoneAuthorizationTestUserSQL, userID, time.Now().UTC()); err != nil {
t.Fatalf("stage tombstone: %v", err)
}
bindConn, err := pool.Acquire(testCtx)
if err != nil {
t.Fatalf("acquire bind connection: %v", err)
}
t.Cleanup(bindConn.Release)
var bindPID int
if err := bindConn.QueryRow(testCtx, "SELECT pg_backend_pid()").Scan(&bindPID); err != nil {
t.Fatalf("get bind backend pid: %v", err)
}
bindResult := make(chan error, 1)
go func() {
bindResult <- NewAuthorizationStore(bindConn).Bind(testCtx, domain.Authorization{AuthKeyID: key, UserID: userID})
}()
waitForPostgresBackendLockWait(t, testCtx, pool, bindPID)
if err := deleteTx.Commit(testCtx); err != nil {
t.Fatalf("commit tombstone: %v", err)
}
select {
case err := <-bindResult:
if !errors.Is(err, domain.ErrAccountDeleted) {
t.Fatalf("Bind after tombstone lock err = %v, want ErrAccountDeleted", err)
}
case <-testCtx.Done():
t.Fatalf("Bind did not finish after tombstone commit: %v", testCtx.Err())
}
assertRevokeTestNoAuthorization(t, testCtx, NewAuthorizationStore(pool), key)
assertRevokeTestTableCount(t, testCtx, pool, "update_states", "auth_key_id", authKeyIDToInt64(key), 0)
}