owpengram-server/internal/store/memory/login_code_invalidate_test.go

106 lines
3.4 KiB
Go

package memory
import (
"context"
"sync"
"testing"
"time"
"telesrv/internal/store"
)
func TestCodeStoreAtomicLoginInvalidation(t *testing.T) {
ctx := context.Background()
const phone = "15550016011"
newRecord := func() store.PhoneCode {
return store.PhoneCode{
Version: store.PhoneCodeVersionCurrent,
Phone: phone,
Code: "12345",
Channel: store.PhoneCodeChannelPhone,
MaxAttempts: 5,
}
}
t.Run("owner cleanup may delete a terminal sign-up marker", func(t *testing.T) {
codes := NewCodeStore()
if err := codes.Set(ctx, "invalidate-marker", newRecord(), time.Minute); err != nil {
t.Fatal(err)
}
verified, err := codes.VerifyLogin(ctx, "invalidate-marker", phone, "12345", true, 5)
if err != nil || verified.Status != store.LoginCodeVerifyAccepted || !verified.Record.SignUpVerified {
t.Fatalf("mark sign-up = %+v err=%v", verified, err)
}
if removed, err := codes.InvalidateLoginCode(ctx, "invalidate-marker", "15550016999"); err != nil || removed {
t.Fatalf("cross-phone invalidate removed=%v err=%v", removed, err)
}
if _, found, err := codes.ConsumeSignUpVerified(ctx, "invalidate-marker", "15550016999"); err != nil || found {
t.Fatalf("cross-phone consume found=%v err=%v", found, err)
}
if removed, err := codes.InvalidateLoginCode(ctx, "invalidate-marker", phone); err != nil || !removed {
t.Fatalf("owner invalidate removed=%v err=%v", removed, err)
}
if _, found, err := codes.ConsumeSignUpVerified(ctx, "invalidate-marker", phone); err != nil || found {
t.Fatalf("consume after invalidate found=%v err=%v", found, err)
}
})
t.Run("legacy records fail closed", func(t *testing.T) {
codes := NewCodeStore()
legacy := newRecord()
legacy.Version = 0
if err := codes.Set(ctx, "invalidate-legacy", legacy, time.Minute); err != nil {
t.Fatal(err)
}
if removed, err := codes.InvalidateLoginCode(ctx, "invalidate-legacy", phone); err != nil || removed {
t.Fatalf("legacy invalidate removed=%v err=%v, want false", removed, err)
}
if _, found, err := codes.Get(ctx, "invalidate-legacy"); err != nil || found {
t.Fatalf("legacy record found=%v err=%v after fail-closed invalidate", found, err)
}
})
t.Run("invalidate and sign-up consume have one winner", func(t *testing.T) {
codes := NewCodeStore()
if err := codes.Set(ctx, "invalidate-race", newRecord(), time.Minute); err != nil {
t.Fatal(err)
}
if verified, err := codes.VerifyLogin(ctx, "invalidate-race", phone, "12345", true, 5); err != nil || verified.Status != store.LoginCodeVerifyAccepted {
t.Fatalf("mark sign-up = %+v err=%v", verified, err)
}
const workers = 64
results := make(chan bool, workers)
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func(invalidate bool) {
defer wg.Done()
if invalidate {
removed, err := codes.InvalidateLoginCode(ctx, "invalidate-race", phone)
if err != nil {
t.Errorf("InvalidateLoginCode: %v", err)
}
results <- removed
return
}
_, found, err := codes.ConsumeSignUpVerified(ctx, "invalidate-race", phone)
if err != nil {
t.Errorf("ConsumeSignUpVerified: %v", err)
}
results <- found
}(i%2 == 0)
}
wg.Wait()
close(results)
winners := 0
for won := range results {
if won {
winners++
}
}
if winners != 1 {
t.Fatalf("invalidate/consume winners=%d, want 1", winners)
}
})
}