mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-08-20 03:46:29 +00:00
174 lines
5.8 KiB
Go
174 lines
5.8 KiB
Go
package usersignup
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/italypaleale/francis/actor"
|
|
"github.com/italypaleale/francis/host/local"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing"
|
|
)
|
|
|
|
// newSignupTokenActorService starts a test actor host with the signup token actor registered and returns its service
|
|
func newSignupTokenActorService(t *testing.T) *actor.Service {
|
|
t.Helper()
|
|
|
|
var svc *actor.Service
|
|
testutils.NewActorHostForTest(t, func(t *testing.T, h *local.Host) {
|
|
err := h.RegisterActor(SignupTokenActorType, NewSignupTokenActor)
|
|
require.NoError(t, err)
|
|
svc = h.Service()
|
|
})
|
|
require.NotNil(t, svc)
|
|
|
|
return svc
|
|
}
|
|
|
|
func createSignupTokenForTest(t *testing.T, svc *actor.Service, token string, state SignupTokenState) {
|
|
t.Helper()
|
|
_, err := svc.Invoke(t.Context(), SignupTokenActorType, token, SignupTokenMethodCreate, state)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func consumeSignupTokenForTest(t *testing.T, svc *actor.Service, token string) signupTokenConsumeResponse {
|
|
t.Helper()
|
|
res, err := svc.Invoke(t.Context(), SignupTokenActorType, token, signupTokenMethodConsume, nil)
|
|
require.NoError(t, err)
|
|
|
|
var out signupTokenConsumeResponse
|
|
err = res.Decode(&out)
|
|
require.NoError(t, err)
|
|
|
|
return out
|
|
}
|
|
|
|
// listSignupTokenIDsForTest returns the actor IDs (that is, the token values) of every stored signup token
|
|
func listSignupTokenIDsForTest(t *testing.T, svc *actor.Service) []string {
|
|
t.Helper()
|
|
res, err := svc.ListStates(t.Context(), SignupTokenActorType, nil)
|
|
require.NoError(t, err)
|
|
|
|
ids := make([]string, len(res.States))
|
|
for i, st := range res.States {
|
|
ids[i] = st.ActorID
|
|
}
|
|
|
|
return ids
|
|
}
|
|
|
|
func TestSignupTokenActorConsume(t *testing.T) {
|
|
svc := newSignupTokenActorService(t)
|
|
|
|
createSignupTokenForTest(t, svc, "token-1", SignupTokenState{
|
|
ID: "id-1",
|
|
ExpiresAt: time.Now().Add(time.Hour),
|
|
UsageLimit: 1,
|
|
UserGroupIDs: []string{"group-a", "group-b"},
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
// First consume succeeds and returns the token's user groups
|
|
res := consumeSignupTokenForTest(t, svc, "token-1")
|
|
require.Equal(t, signupTokenConsumeOK, res.Status)
|
|
require.Equal(t, []string{"group-a", "group-b"}, res.UserGroupIDs)
|
|
|
|
// Second consume fails: the usage limit (1) has been reached
|
|
res = consumeSignupTokenForTest(t, svc, "token-1")
|
|
require.Equal(t, signupTokenConsumeLimitReached, res.Status)
|
|
}
|
|
|
|
func TestSignupTokenActorConsumeNotFound(t *testing.T) {
|
|
svc := newSignupTokenActorService(t)
|
|
|
|
res := consumeSignupTokenForTest(t, svc, "does-not-exist")
|
|
require.Equal(t, signupTokenConsumeNotFound, res.Status)
|
|
}
|
|
|
|
// TestSignupTokenActorCreateExpired verifies that a token that has already expired is never stored, since its state TTL would be in the past
|
|
func TestSignupTokenActorCreateExpired(t *testing.T) {
|
|
svc := newSignupTokenActorService(t)
|
|
|
|
createSignupTokenForTest(t, svc, "token-expired", SignupTokenState{
|
|
ID: "id-expired",
|
|
ExpiresAt: time.Now().Add(-time.Minute),
|
|
UsageLimit: 1,
|
|
CreatedAt: time.Now().Add(-time.Hour),
|
|
})
|
|
|
|
require.Empty(t, listSignupTokenIDsForTest(t, svc))
|
|
require.Equal(t, signupTokenConsumeNotFound, consumeSignupTokenForTest(t, svc, "token-expired").Status)
|
|
}
|
|
|
|
func TestSignupTokenActorRelease(t *testing.T) {
|
|
svc := newSignupTokenActorService(t)
|
|
|
|
createSignupTokenForTest(t, svc, "token-2", SignupTokenState{
|
|
ID: "id-2",
|
|
ExpiresAt: time.Now().Add(time.Hour),
|
|
UsageLimit: 2,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
// Consume both uses
|
|
require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc, "token-2").Status)
|
|
require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc, "token-2").Status)
|
|
require.Equal(t, signupTokenConsumeLimitReached, consumeSignupTokenForTest(t, svc, "token-2").Status)
|
|
|
|
// Release one use (compensation)
|
|
_, err := svc.Invoke(t.Context(), SignupTokenActorType, "token-2", signupTokenMethodRelease, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Consuming succeeds again now that a use was released
|
|
require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc, "token-2").Status)
|
|
}
|
|
|
|
func TestSignupTokenActorDelete(t *testing.T) {
|
|
svc := newSignupTokenActorService(t)
|
|
|
|
createSignupTokenForTest(t, svc, "token-3", SignupTokenState{
|
|
ID: "id-3",
|
|
ExpiresAt: time.Now().Add(time.Hour),
|
|
UsageLimit: 1,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
require.Equal(t, []string{"token-3"}, listSignupTokenIDsForTest(t, svc))
|
|
|
|
_, err := svc.Invoke(t.Context(), SignupTokenActorType, "token-3", SignupTokenMethodDelete, nil)
|
|
require.NoError(t, err)
|
|
|
|
require.Empty(t, listSignupTokenIDsForTest(t, svc))
|
|
|
|
// The token can no longer be consumed
|
|
require.Equal(t, signupTokenConsumeNotFound, consumeSignupTokenForTest(t, svc, "token-3").Status)
|
|
|
|
// Deleting a token that no longer exists is a no-op
|
|
_, err = svc.Invoke(t.Context(), SignupTokenActorType, "token-3", SignupTokenMethodDelete, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// TestSignupTokenActorMigrateDoesNotOverwrite verifies that the one-time migration never resets a token that was already migrated and used since
|
|
func TestSignupTokenActorMigrateDoesNotOverwrite(t *testing.T) {
|
|
svc := newSignupTokenActorService(t)
|
|
|
|
state := SignupTokenState{
|
|
ID: "id-4",
|
|
ExpiresAt: time.Now().Add(time.Hour),
|
|
UsageLimit: 2,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
createSignupTokenForTest(t, svc, "token-4", state)
|
|
|
|
// Use the token once
|
|
require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc, "token-4").Status)
|
|
|
|
// Re-running the migration must not reset the usage count
|
|
_, err := svc.Invoke(t.Context(), SignupTokenActorType, "token-4", signupTokenMethodMigrate, state)
|
|
require.NoError(t, err)
|
|
|
|
// Only one use is left, so a single consume succeeds and the next one doesn't
|
|
require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc, "token-4").Status)
|
|
require.Equal(t, signupTokenConsumeLimitReached, consumeSignupTokenForTest(t, svc, "token-4").Status)
|
|
}
|