feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
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
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
This commit is contained in:
parent
5cd1de6859
commit
708e20f747
406 changed files with 42464 additions and 6199 deletions
232
apps/desktop/tests/red/auth-login.usecase.test.ts
Normal file
232
apps/desktop/tests/red/auth-login.usecase.test.ts
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
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)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue