| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- package repository
- import (
- "context"
- "sync"
- "testing"
- "time"
- dbent "github.com/Wei-Shaw/sub2api/ent"
- "github.com/Wei-Shaw/sub2api/ent/authidentity"
- "github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
- "github.com/Wei-Shaw/sub2api/ent/identityadoptiondecision"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "github.com/stretchr/testify/require"
- )
- func TestUserRepositoryBindAuthIdentityToUserCanonicalizesLegacyWeChatAlias(t *testing.T) {
- repo, client := newUserEntRepo(t)
- ctx := context.Background()
- user := &service.User{
- Email: "wechat-legacy@example.com",
- Username: "wechat-legacy",
- PasswordHash: "hash",
- Role: service.RoleUser,
- Status: service.StatusActive,
- }
- require.NoError(t, repo.Create(ctx, user))
- legacyIdentity, err := client.AuthIdentity.Create().
- SetUserID(user.ID).
- SetProviderType("wechat").
- SetProviderKey("wechat").
- SetProviderSubject("union-legacy-123").
- SetMetadata(map[string]any{"source": "legacy-alias"}).
- Save(ctx)
- require.NoError(t, err)
- legacyChannel, err := client.AuthIdentityChannel.Create().
- SetIdentityID(legacyIdentity.ID).
- SetProviderType("wechat").
- SetProviderKey("wechat").
- SetChannel("oa").
- SetChannelAppID("wx-app-legacy").
- SetChannelSubject("openid-legacy-123").
- SetMetadata(map[string]any{"scene": "legacy-alias"}).
- Save(ctx)
- require.NoError(t, err)
- bound, err := repo.BindAuthIdentityToUser(ctx, BindAuthIdentityInput{
- UserID: user.ID,
- Canonical: AuthIdentityKey{
- ProviderType: "wechat",
- ProviderKey: "wechat-main",
- ProviderSubject: "union-legacy-123",
- },
- Channel: &AuthIdentityChannelKey{
- ProviderType: "wechat",
- ProviderKey: "wechat-main",
- Channel: "oa",
- ChannelAppID: "wx-app-legacy",
- ChannelSubject: "openid-legacy-123",
- },
- Metadata: map[string]any{"source": "canonical-bind"},
- ChannelMetadata: map[string]any{"scene": "canonical-bind"},
- })
- require.NoError(t, err)
- require.NotNil(t, bound)
- require.NotNil(t, bound.Identity)
- require.NotNil(t, bound.Channel)
- require.Equal(t, legacyIdentity.ID, bound.Identity.ID)
- require.Equal(t, legacyChannel.ID, bound.Channel.ID)
- require.Equal(t, "wechat-main", bound.Identity.ProviderKey)
- require.Equal(t, "wechat-main", bound.Channel.ProviderKey)
- reloadedIdentity, err := client.AuthIdentity.Get(ctx, legacyIdentity.ID)
- require.NoError(t, err)
- require.Equal(t, "wechat-main", reloadedIdentity.ProviderKey)
- require.Equal(t, "canonical-bind", reloadedIdentity.Metadata["source"])
- reloadedChannel, err := client.AuthIdentityChannel.Get(ctx, legacyChannel.ID)
- require.NoError(t, err)
- require.Equal(t, "wechat-main", reloadedChannel.ProviderKey)
- require.Equal(t, "canonical-bind", reloadedChannel.Metadata["scene"])
- identityCount, err := client.AuthIdentity.Query().
- Where(
- authidentity.UserIDEQ(user.ID),
- authidentity.ProviderTypeEQ("wechat"),
- authidentity.ProviderSubjectEQ("union-legacy-123"),
- ).
- Count(ctx)
- require.NoError(t, err)
- require.Equal(t, 1, identityCount)
- channelCount, err := client.AuthIdentityChannel.Query().
- Where(
- authidentitychannel.ProviderTypeEQ("wechat"),
- authidentitychannel.ChannelEQ("oa"),
- authidentitychannel.ChannelAppIDEQ("wx-app-legacy"),
- authidentitychannel.ChannelSubjectEQ("openid-legacy-123"),
- ).
- Count(ctx)
- require.NoError(t, err)
- require.Equal(t, 1, channelCount)
- }
- func TestUserRepositoryUpsertIdentityAdoptionDecisionIsIdempotentUnderConcurrency(t *testing.T) {
- repo, client := newUserEntRepo(t)
- ctx := context.Background()
- user := &service.User{
- Email: "repo-adoption@example.com",
- Username: "repo-adoption",
- PasswordHash: "hash",
- Role: service.RoleUser,
- Status: service.StatusActive,
- }
- require.NoError(t, repo.Create(ctx, user))
- identity, err := client.AuthIdentity.Create().
- SetUserID(user.ID).
- SetProviderType("wechat").
- SetProviderKey("wechat-main").
- SetProviderSubject("union-repo-adoption").
- SetMetadata(map[string]any{}).
- Save(ctx)
- require.NoError(t, err)
- session, err := client.PendingAuthSession.Create().
- SetSessionToken("pending-repo-adoption").
- SetIntent("bind_current_user").
- SetProviderType("wechat").
- SetProviderKey("wechat-main").
- SetProviderSubject("union-repo-adoption").
- SetExpiresAt(time.Now().UTC().Add(15 * time.Minute)).
- SetUpstreamIdentityClaims(map[string]any{"provider_subject": "union-repo-adoption"}).
- SetLocalFlowState(map[string]any{"step": "pending"}).
- Save(ctx)
- require.NoError(t, err)
- firstCreateStarted := make(chan struct{})
- releaseFirstCreate := make(chan struct{})
- var firstCreate sync.Once
- client.IdentityAdoptionDecision.Use(func(next dbent.Mutator) dbent.Mutator {
- return dbent.MutateFunc(func(ctx context.Context, m dbent.Mutation) (dbent.Value, error) {
- blocked := false
- if m.Op().Is(dbent.OpCreate) {
- firstCreate.Do(func() {
- blocked = true
- close(firstCreateStarted)
- })
- }
- if blocked {
- <-releaseFirstCreate
- }
- return next.Mutate(ctx, m)
- })
- })
- type adoptionResult struct {
- decision *dbent.IdentityAdoptionDecision
- err error
- }
- input := IdentityAdoptionDecisionInput{
- PendingAuthSessionID: session.ID,
- IdentityID: &identity.ID,
- AdoptDisplayName: true,
- AdoptAvatar: true,
- }
- results := make(chan adoptionResult, 2)
- go func() {
- decision, err := repo.UpsertIdentityAdoptionDecision(ctx, input)
- results <- adoptionResult{decision: decision, err: err}
- }()
- <-firstCreateStarted
- go func() {
- decision, err := repo.UpsertIdentityAdoptionDecision(ctx, input)
- results <- adoptionResult{decision: decision, err: err}
- }()
- time.Sleep(100 * time.Millisecond)
- close(releaseFirstCreate)
- first := <-results
- second := <-results
- require.NoError(t, first.err)
- require.NoError(t, second.err)
- require.NotNil(t, first.decision)
- require.NotNil(t, second.decision)
- require.Equal(t, first.decision.ID, second.decision.ID)
- count, err := client.IdentityAdoptionDecision.Query().
- Where(identityadoptiondecision.PendingAuthSessionIDEQ(session.ID)).
- Count(ctx)
- require.NoError(t, err)
- require.Equal(t, 1, count)
- loaded, err := client.IdentityAdoptionDecision.Query().
- Where(identityadoptiondecision.PendingAuthSessionIDEQ(session.ID)).
- Only(ctx)
- require.NoError(t, err)
- require.NotNil(t, loaded.IdentityID)
- require.Equal(t, identity.ID, *loaded.IdentityID)
- require.True(t, loaded.AdoptDisplayName)
- require.True(t, loaded.AdoptAvatar)
- }
|