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

This commit is contained in:
Yun Chan 2026-08-20 11:12:05 +09:00
parent 5cd1de6859
commit 708e20f747
406 changed files with 42464 additions and 6199 deletions

View 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)
})
})