diff --git a/backend/internal/auth/service_test.go b/backend/internal/auth/service_test.go new file mode 100644 index 0000000..9fc91a5 --- /dev/null +++ b/backend/internal/auth/service_test.go @@ -0,0 +1,190 @@ +package auth_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/hhs/camtalk/internal/auth" + "github.com/hhs/camtalk/internal/store" +) + +// newTestService 创建测试用的 AuthService + MemUserRepository。 +func newTestService(t *testing.T) (auth.Service, *store.MemUserRepository) { + t.Helper() + tm := auth.NewTokenManager("test-secret", 15*time.Minute, 7*24*time.Hour) + repo := store.NewMemUserRepository() + svc := auth.NewAuthService(tm, repo) + return svc, repo +} + +// --- Register --- + +func TestRegister_Success(t *testing.T) { + svc, _ := newTestService(t) + ctx := context.Background() + + resp, err := svc.Register(ctx, auth.RegisterRequest{ + Username: "alice", + Password: "password123", + }) + require.NoError(t, err) + assert.NotEmpty(t, resp.User.ID) + assert.Equal(t, "alice", resp.User.Username) + assert.NotEmpty(t, resp.AccessToken) + assert.NotEmpty(t, resp.RefreshToken) +} + +func TestRegister_DuplicateUsername(t *testing.T) { + svc, _ := newTestService(t) + ctx := context.Background() + + _, err := svc.Register(ctx, auth.RegisterRequest{ + Username: "alice", + Password: "password123", + }) + require.NoError(t, err) + + // 同名再次注册 + _, err = svc.Register(ctx, auth.RegisterRequest{ + Username: "alice", + Password: "another-password", + }) + assert.ErrorIs(t, err, auth.ErrUsernameTaken) +} + +// --- Login --- + +func TestLogin_Success(t *testing.T) { + svc, _ := newTestService(t) + ctx := context.Background() + + // 先注册 + _, err := svc.Register(ctx, auth.RegisterRequest{ + Username: "bob", + Password: "password123", + }) + require.NoError(t, err) + + // 登录 + resp, err := svc.Login(ctx, auth.LoginRequest{ + Username: "bob", + Password: "password123", + }) + require.NoError(t, err) + assert.Equal(t, "bob", resp.User.Username) + assert.NotEmpty(t, resp.AccessToken) + assert.NotEmpty(t, resp.RefreshToken) +} + +func TestLogin_WrongPassword(t *testing.T) { + svc, _ := newTestService(t) + ctx := context.Background() + + _, err := svc.Register(ctx, auth.RegisterRequest{ + Username: "bob", + Password: "password123", + }) + require.NoError(t, err) + + _, err = svc.Login(ctx, auth.LoginRequest{ + Username: "bob", + Password: "wrong-password", + }) + assert.ErrorIs(t, err, auth.ErrInvalidCredentials) +} + +func TestLogin_UserNotFound(t *testing.T) { + svc, _ := newTestService(t) + ctx := context.Background() + + _, err := svc.Login(ctx, auth.LoginRequest{ + Username: "nonexistent", + Password: "password123", + }) + assert.ErrorIs(t, err, auth.ErrInvalidCredentials) +} + +// --- Refresh --- + +func TestRefresh_Success(t *testing.T) { + svc, _ := newTestService(t) + ctx := context.Background() + + // 注册 + regResp, err := svc.Register(ctx, auth.RegisterRequest{ + Username: "charlie", + Password: "password123", + }) + require.NoError(t, err) + + // 刷新 + refreshResp, err := svc.Refresh(ctx, auth.RefreshRequest{ + RefreshToken: regResp.RefreshToken, + }) + require.NoError(t, err) + assert.Equal(t, "charlie", refreshResp.User.Username) + assert.NotEmpty(t, refreshResp.AccessToken) + assert.NotEmpty(t, refreshResp.RefreshToken) + // 新旧 refresh token 应不同(rotation) + assert.NotEqual(t, regResp.RefreshToken, refreshResp.RefreshToken) +} + +func TestRefresh_UsedTokenFails(t *testing.T) { + svc, _ := newTestService(t) + ctx := context.Background() + + regResp, err := svc.Register(ctx, auth.RegisterRequest{ + Username: "charlie", + Password: "password123", + }) + require.NoError(t, err) + + // 第一次刷新 + _, err = svc.Refresh(ctx, auth.RefreshRequest{ + RefreshToken: regResp.RefreshToken, + }) + require.NoError(t, err) + + // 用旧 token 再次刷新 → 应失败 + _, err = svc.Refresh(ctx, auth.RefreshRequest{ + RefreshToken: regResp.RefreshToken, + }) + assert.ErrorIs(t, err, auth.ErrRefreshTokenUsed) +} + +func TestRefresh_InvalidToken(t *testing.T) { + svc, _ := newTestService(t) + ctx := context.Background() + + _, err := svc.Refresh(ctx, auth.RefreshRequest{ + RefreshToken: "completely-invalid-token", + }) + assert.ErrorIs(t, err, auth.ErrRefreshTokenUsed) +} + +// --- Logout --- + +func TestLogout_Success(t *testing.T) { + svc, _ := newTestService(t) + ctx := context.Background() + + regResp, err := svc.Register(ctx, auth.RegisterRequest{ + Username: "dave", + Password: "password123", + }) + require.NoError(t, err) + + // 登出 + err = svc.Logout(ctx, regResp.User.ID, regResp.RefreshToken) + require.NoError(t, err) + + // 登出后 refresh token 应失效 + _, err = svc.Refresh(ctx, auth.RefreshRequest{ + RefreshToken: regResp.RefreshToken, + }) + assert.ErrorIs(t, err, auth.ErrRefreshTokenUsed) +}