89 lines
3.4 KiB
Go
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)
|
|
}
|