Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
232 lines
8 KiB
TypeScript
232 lines
8 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest'
|
|
import { ErrorCode } from '@d3ro/core/errors'
|
|
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
|
|
import { configGet, configSet } from '../../src/main/services/ConfigService'
|
|
import { getOnlineLLMService } from '../../src/main/services/OnlineLLMService'
|
|
import { getCloudSyncService } from '../../src/main/services/CloudSyncService'
|
|
import { registerLLMHandlers } from '../../src/main/ipc/llm-handlers'
|
|
import { registerCloudSyncHandlers } from '../../src/main/ipc/cloud-sync-handlers'
|
|
import { invokeIpc, useRedHarness } from './harness'
|
|
import { FX } from './fixtures'
|
|
|
|
useRedHarness()
|
|
|
|
describe('유스케이스: 온라인 로그인/가입/로그아웃 + 클라우드 로그인 실패', () => {
|
|
it('로그인 전에는 GET_USER 가 비인증이다', async () => {
|
|
registerLLMHandlers()
|
|
const res = await invokeIpc(IPC_CHANNELS.ONLINE_AUTH.GET_USER)
|
|
expect(res.success).toBe(true)
|
|
if (res.success) {
|
|
expect(res.data.isAuthenticated).toBe(false)
|
|
expect(res.data.email).toBeNull()
|
|
}
|
|
})
|
|
|
|
it('로그인 성공 시 토큰이 저장되고 인증 상태가 true 다', async () => {
|
|
registerLLMHandlers()
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => ({
|
|
ok: true,
|
|
json: async () => ({ token: FX.AUTH_TOKEN, email: FX.AUTH_EMAIL }),
|
|
})),
|
|
)
|
|
const res = await invokeIpc(IPC_CHANNELS.ONLINE_AUTH.LOGIN, {
|
|
email: FX.AUTH_EMAIL,
|
|
password: 'fx-not-the-asserted-secret',
|
|
})
|
|
expect(res.success).toBe(true)
|
|
expect(configGet('authToken')).toBe(FX.AUTH_TOKEN)
|
|
expect(configGet('userEmail')).toBe(FX.AUTH_EMAIL)
|
|
expect(configGet('llmBackend')).toBe('online')
|
|
const user = await invokeIpc(IPC_CHANNELS.ONLINE_AUTH.GET_USER)
|
|
if (user.success) expect(user.data.isAuthenticated).toBe(true)
|
|
})
|
|
|
|
it('로그인 실패(4xx)는 토큰을 남기지 않고 에러를 반환한다', async () => {
|
|
registerLLMHandlers()
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => ({
|
|
ok: false,
|
|
json: async () => ({ message: FX.AUTH_FAIL_MSG }),
|
|
})),
|
|
)
|
|
const res = await invokeIpc(IPC_CHANNELS.ONLINE_AUTH.LOGIN, {
|
|
email: FX.AUTH_EMAIL,
|
|
password: 'wrong',
|
|
})
|
|
expect(res.success).toBe(false)
|
|
if (!res.success) {
|
|
expect(res.error.code).toBe(ErrorCode.LLMProcessingFailed)
|
|
expect(res.error.message).toContain(FX.AUTH_FAIL_MSG)
|
|
}
|
|
expect(configGet('authToken')).toBeNull()
|
|
})
|
|
|
|
it('서버 연결 실패는 LLMServerUnreachable 이다', async () => {
|
|
registerLLMHandlers()
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => {
|
|
throw new Error('ECONNREFUSED')
|
|
}),
|
|
)
|
|
const res = await invokeIpc(IPC_CHANNELS.ONLINE_AUTH.LOGIN, {
|
|
email: FX.AUTH_EMAIL,
|
|
password: 'x',
|
|
})
|
|
expect(res.success).toBe(false)
|
|
if (!res.success) expect(res.error.code).toBe(ErrorCode.LLMServerUnreachable)
|
|
})
|
|
|
|
it('로그인 실패 후 재시도 성공이 토큰을 저장한다', async () => {
|
|
registerLLMHandlers()
|
|
const fetchMock = vi
|
|
.fn()
|
|
.mockResolvedValueOnce({
|
|
ok: false,
|
|
json: async () => ({ message: FX.AUTH_FAIL_MSG }),
|
|
})
|
|
.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({ token: FX.AUTH_TOKEN, email: FX.AUTH_EMAIL }),
|
|
})
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
const fail = await invokeIpc(IPC_CHANNELS.ONLINE_AUTH.LOGIN, { email: 'a', password: 'b' })
|
|
expect(fail.success).toBe(false)
|
|
const ok = await invokeIpc(IPC_CHANNELS.ONLINE_AUTH.LOGIN, { email: 'a', password: 'b' })
|
|
expect(ok.success).toBe(true)
|
|
expect(configGet('authToken')).toBe(FX.AUTH_TOKEN)
|
|
})
|
|
|
|
it('가입 성공은 로그인과 같이 토큰을 저장한다', async () => {
|
|
registerLLMHandlers()
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => ({
|
|
ok: true,
|
|
json: async () => ({ token: FX.AUTH_TOKEN, email: FX.AUTH_EMAIL }),
|
|
})),
|
|
)
|
|
const res = await invokeIpc(IPC_CHANNELS.ONLINE_AUTH.REGISTER, {
|
|
email: FX.AUTH_EMAIL,
|
|
password: 'pw',
|
|
})
|
|
expect(res.success).toBe(true)
|
|
expect(configGet('authToken')).toBe(FX.AUTH_TOKEN)
|
|
})
|
|
|
|
it('가입 실패는 토큰을 쓰지 않는다', async () => {
|
|
registerLLMHandlers()
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => ({
|
|
ok: false,
|
|
json: async () => ({ message: 'email taken' }),
|
|
})),
|
|
)
|
|
const res = await invokeIpc(IPC_CHANNELS.ONLINE_AUTH.REGISTER, {
|
|
email: FX.AUTH_EMAIL,
|
|
password: 'pw',
|
|
})
|
|
expect(res.success).toBe(false)
|
|
expect(configGet('authToken')).toBeNull()
|
|
})
|
|
|
|
it('로그아웃은 토큰과 이메일을 지운다', async () => {
|
|
registerLLMHandlers()
|
|
configSet('authToken', FX.AUTH_TOKEN)
|
|
configSet('userEmail', FX.AUTH_EMAIL)
|
|
const res = await invokeIpc(IPC_CHANNELS.ONLINE_AUTH.LOGOUT)
|
|
expect(res.success).toBe(true)
|
|
expect(configGet('authToken')).toBeNull()
|
|
expect(configGet('userEmail')).toBeNull()
|
|
const user = await invokeIpc(IPC_CHANNELS.ONLINE_AUTH.GET_USER)
|
|
if (user.success) expect(user.data.isAuthenticated).toBe(false)
|
|
})
|
|
|
|
it('토큰 없이 OnlineLLM.processText 는 로그인 필요 에러다', async () => {
|
|
await expect(
|
|
getOnlineLLMService().processText('hello', 'refine'),
|
|
).rejects.toMatchObject({ code: ErrorCode.LLMServerUnreachable })
|
|
})
|
|
|
|
it('isAvailable 은 토큰이 있을 때만 true 다', () => {
|
|
expect(getOnlineLLMService().isAvailable()).toBe(false)
|
|
configSet('authToken', FX.AUTH_TOKEN)
|
|
expect(getOnlineLLMService().isAvailable()).toBe(true)
|
|
})
|
|
|
|
it('OnlineLLM 이 401 을 받으면 토큰을 지우고 에러를 던진다', async () => {
|
|
configSet('authToken', FX.AUTH_TOKEN)
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => ({
|
|
ok: false,
|
|
status: 401,
|
|
json: async () => ({}),
|
|
})),
|
|
)
|
|
await expect(getOnlineLLMService().processText('hi', 'refine')).rejects.toMatchObject({
|
|
code: ErrorCode.LLMServerUnreachable,
|
|
})
|
|
expect(configGet('authToken')).toBeNull()
|
|
})
|
|
|
|
it('OnlineLLM 이 빈 텍스트를 반환하면 성공으로 위장하지 않는다', async () => {
|
|
configSet('authToken', FX.AUTH_TOKEN)
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ text: '' }),
|
|
})),
|
|
)
|
|
await expect(getOnlineLLMService().processText('hi', 'refine')).rejects.toMatchObject({
|
|
code: ErrorCode.LLMProcessingFailed,
|
|
})
|
|
})
|
|
|
|
it('CloudSync 미초기화 상태에서 startSignIn 은 설정 안 됨 에러다', async () => {
|
|
await expect(getCloudSyncService().startSignIn('google')).rejects.toMatchObject({
|
|
code: ErrorCode.LLMServerUnreachable,
|
|
})
|
|
})
|
|
|
|
it('CloudSync handleAuthCallback 빈 코드는 실패한다', async () => {
|
|
await expect(getCloudSyncService().handleAuthCallback('')).rejects.toBeTruthy()
|
|
})
|
|
|
|
it('CloudSync 미인증 getState 는 authenticated=false', () => {
|
|
const state = getCloudSyncService().getState()
|
|
expect(state.authenticated).toBe(false)
|
|
expect(state.userEmail).toBeNull()
|
|
})
|
|
|
|
it('미인증 pushAll 은 로그인 필요 에러다', async () => {
|
|
await expect(getCloudSyncService().pushAll()).rejects.toMatchObject({
|
|
code: ErrorCode.LLMServerUnreachable,
|
|
})
|
|
})
|
|
|
|
it('미인증 pullAll 은 로그인 필요 에러다', async () => {
|
|
await expect(getCloudSyncService().pullAll()).rejects.toMatchObject({
|
|
code: ErrorCode.LLMServerUnreachable,
|
|
})
|
|
})
|
|
|
|
it('IPC cloudSync:signIn 은 미설정에서 실패를 숨기지 않는다', async () => {
|
|
registerCloudSyncHandlers()
|
|
const res = await invokeIpc(IPC_CHANNELS.CLOUD_SYNC.SIGN_IN, { provider: 'google' })
|
|
expect(res.success).toBe(false)
|
|
})
|
|
|
|
it('IPC cloudSync:getState 는 비인증이다', async () => {
|
|
registerCloudSyncHandlers()
|
|
const res = await invokeIpc(IPC_CHANNELS.CLOUD_SYNC.GET_STATE)
|
|
expect(res.success).toBe(true)
|
|
if (res.success) expect(res.data.authenticated).toBe(false)
|
|
})
|
|
})
|