171 lines
6.0 KiB
Go
171 lines
6.0 KiB
Go
package usecase_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"apps/backend/internal/module/threads/domain"
|
|
"apps/backend/internal/module/threads/provider"
|
|
"apps/backend/internal/module/threads/repository"
|
|
"apps/backend/internal/module/threads/usecase"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newThreadsSvc(failEx, failRef bool) (*usecase.Service, *repository.MemoryStore, *provider.FakeProvider) {
|
|
store := repository.NewMemory()
|
|
fake := &provider.FakeProvider{
|
|
CallbackBase: "http://127.0.0.1:8888/api/v1/threads-accounts/oauth/callback",
|
|
FailExchange: failEx,
|
|
FailRefresh: failRef,
|
|
}
|
|
svc := usecase.New(store, fake, "test-secret-key")
|
|
svc.APIPublicBase = "http://127.0.0.1:8888"
|
|
svc.WebBase = "http://127.0.0.1:5173"
|
|
return svc, store, fake
|
|
}
|
|
|
|
func TestTH_01_OAuthStart(t *testing.T) {
|
|
svc, _, _ := newThreadsSvc(false, false)
|
|
url, state, err := svc.OAuthStart(context.Background(), 1_000_001)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, state)
|
|
require.Contains(t, url, "state=")
|
|
require.Contains(t, url, "code=fake")
|
|
}
|
|
|
|
func TestTH_02_CallbackSuccess(t *testing.T) {
|
|
svc, store, _ := newThreadsSvc(false, false)
|
|
uid := int64(1_000_002)
|
|
_, state, err := svc.OAuthStart(context.Background(), uid)
|
|
require.NoError(t, err)
|
|
acc, err := svc.OAuthCallback(context.Background(), "fake", state, "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, domain.ConnectionConnected, acc.Connection)
|
|
require.True(t, acc.IsUsable)
|
|
require.Equal(t, uid, acc.OwnerUID)
|
|
require.NotEmpty(t, acc.AccessTokenEnc)
|
|
// token not equal plaintext in public sense
|
|
require.NotContains(t, acc.AccessTokenEnc, "fake-access")
|
|
list, err := store.ListByOwner(context.Background(), uid)
|
|
require.NoError(t, err)
|
|
require.Len(t, list, 1)
|
|
}
|
|
|
|
func TestTH_03_UserDenied(t *testing.T) {
|
|
svc, _, _ := newThreadsSvc(false, false)
|
|
_, state, err := svc.OAuthStart(context.Background(), 1_000_003)
|
|
require.NoError(t, err)
|
|
_, err = svc.OAuthCallback(context.Background(), "", state, "access_denied")
|
|
require.ErrorIs(t, err, domain.ErrOAuthDenied)
|
|
}
|
|
|
|
func TestTH_04_TokenExchangeFail(t *testing.T) {
|
|
svc, _, _ := newThreadsSvc(true, false)
|
|
_, state, err := svc.OAuthStart(context.Background(), 1_000_004)
|
|
require.NoError(t, err)
|
|
_, err = svc.OAuthCallback(context.Background(), "fake", state, "")
|
|
require.ErrorIs(t, err, domain.ErrOAuthExchange)
|
|
}
|
|
|
|
func TestTH_05_List(t *testing.T) {
|
|
svc, _, _ := newThreadsSvc(false, false)
|
|
uid := int64(1_000_005)
|
|
_, st, _ := svc.OAuthStart(context.Background(), uid)
|
|
_, err := svc.OAuthCallback(context.Background(), "fake", st, "")
|
|
require.NoError(t, err)
|
|
list, err := svc.List(context.Background(), uid)
|
|
require.NoError(t, err)
|
|
require.Len(t, list, 1)
|
|
require.True(t, list[0].SessionExpiresAt > 0)
|
|
}
|
|
|
|
func TestTH_06_RefreshOk(t *testing.T) {
|
|
svc, _, _ := newThreadsSvc(false, false)
|
|
uid := int64(1_000_006)
|
|
_, st, _ := svc.OAuthStart(context.Background(), uid)
|
|
acc, err := svc.OAuthCallback(context.Background(), "fake", st, "")
|
|
require.NoError(t, err)
|
|
out, err := svc.Refresh(context.Background(), uid, acc.ID)
|
|
require.NoError(t, err)
|
|
require.True(t, out.IsUsable)
|
|
require.Equal(t, domain.ConnectionConnected, out.Connection)
|
|
require.True(t, out.SessionRefreshedAt >= acc.SessionRefreshedAt)
|
|
}
|
|
|
|
func TestTH_07_RefreshFail(t *testing.T) {
|
|
svc, store, fake := newThreadsSvc(false, false)
|
|
uid := int64(1_000_007)
|
|
_, st, _ := svc.OAuthStart(context.Background(), uid)
|
|
acc, err := svc.OAuthCallback(context.Background(), "fake", st, "")
|
|
require.NoError(t, err)
|
|
fake.FailRefresh = true
|
|
// poison refresh token
|
|
acc.RefreshTokenEnc, _ = domain.Seal("test-secret-key", "bad-token")
|
|
require.NoError(t, store.Update(context.Background(), acc))
|
|
out, err := svc.Refresh(context.Background(), uid, acc.ID)
|
|
require.ErrorIs(t, err, domain.ErrRefreshFailed)
|
|
require.False(t, out.IsUsable)
|
|
require.Equal(t, domain.ConnectionError, out.Connection)
|
|
}
|
|
|
|
func TestTH_08_Disconnect(t *testing.T) {
|
|
svc, _, _ := newThreadsSvc(false, false)
|
|
uid := int64(1_000_008)
|
|
_, st, _ := svc.OAuthStart(context.Background(), uid)
|
|
acc, err := svc.OAuthCallback(context.Background(), "fake", st, "")
|
|
require.NoError(t, err)
|
|
require.NoError(t, svc.Disconnect(context.Background(), uid, acc.ID))
|
|
list, err := svc.List(context.Background(), uid)
|
|
require.NoError(t, err)
|
|
require.Empty(t, list)
|
|
}
|
|
|
|
func TestTH_11_ReconnectUpsertsSession(t *testing.T) {
|
|
svc, _, fake := newThreadsSvc(false, false)
|
|
fake.FixedUserID = "threads_uid_fixed"
|
|
fake.FixedUsername = "harbor_main"
|
|
uid := int64(1_000_012)
|
|
|
|
_, st1, _ := svc.OAuthStart(context.Background(), uid)
|
|
acc1, err := svc.OAuthCallback(context.Background(), "fake", st1, "")
|
|
require.NoError(t, err)
|
|
oldTok := acc1.AccessTokenEnc
|
|
|
|
_, st2, _ := svc.OAuthStart(context.Background(), uid)
|
|
acc2, err := svc.OAuthCallback(context.Background(), "fake", st2, "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, acc1.ID, acc2.ID, "same Threads user must keep same account id")
|
|
require.NotEqual(t, oldTok, acc2.AccessTokenEnc, "session tokens must be refreshed")
|
|
require.True(t, acc2.IsUsable)
|
|
require.Equal(t, domain.ConnectionConnected, acc2.Connection)
|
|
|
|
list, err := svc.List(context.Background(), uid)
|
|
require.NoError(t, err)
|
|
require.Len(t, list, 1)
|
|
}
|
|
|
|
func TestTH_09_RequiresCallbackNotBypass(t *testing.T) {
|
|
// Live completion requires OAuthCallback path (has encrypted token).
|
|
svc, _, _ := newThreadsSvc(false, false)
|
|
uid := int64(1_000_009)
|
|
_, st, _ := svc.OAuthStart(context.Background(), uid)
|
|
acc, err := svc.OAuthCallback(context.Background(), "fake", st, "")
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, acc.AccessTokenEnc)
|
|
require.True(t, acc.IsUsable)
|
|
}
|
|
|
|
func TestTH_10_CrossUidDenied(t *testing.T) {
|
|
svc, _, _ := newThreadsSvc(false, false)
|
|
uidA := int64(1_000_010)
|
|
uidB := int64(1_000_011)
|
|
_, st, _ := svc.OAuthStart(context.Background(), uidA)
|
|
acc, err := svc.OAuthCallback(context.Background(), "fake", st, "")
|
|
require.NoError(t, err)
|
|
_, err = svc.Refresh(context.Background(), uidB, acc.ID)
|
|
require.ErrorIs(t, err, domain.ErrForbidden)
|
|
err = svc.Disconnect(context.Background(), uidB, acc.ID)
|
|
require.ErrorIs(t, err, domain.ErrForbidden)
|
|
}
|