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
192
apps/desktop/tests/red/license.usecase.test.ts
Normal file
192
apps/desktop/tests/red/license.usecase.test.ts
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { ErrorCode } from '@d3ro/core/errors'
|
||||
import { Feature } from '@d3ro/core/types'
|
||||
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
|
||||
import { getLicenseService } from '../../src/main/services/LicenseService'
|
||||
import { registerLicenseHandlers } from '../../src/main/ipc/license-handlers'
|
||||
import { invokeIpc, useRedHarness } from './harness'
|
||||
import { FX } from './fixtures'
|
||||
|
||||
useRedHarness()
|
||||
|
||||
describe('유스케이스: 라이선스 활성화 / 기능 게이트 / 쿼터 / IPC', () => {
|
||||
it('초기 티어는 free 이다', () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
expect(svc.getInfo().tier).toBe('free')
|
||||
expect(svc.tier).toBe('free')
|
||||
})
|
||||
|
||||
it('빈 키 활성화는 실패하고 free 를 유지한다', async () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
const result = await svc.activate(' ')
|
||||
expect(result.success).toBe(false)
|
||||
expect(svc.tier).toBe('free')
|
||||
})
|
||||
|
||||
it('잘못된 키 활성화는 실패한다', async () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
const result = await svc.activate(FX.LICENSE_BAD)
|
||||
expect(result.success).toBe(false)
|
||||
expect(svc.getInfo().licenseKey).toBeNull()
|
||||
})
|
||||
|
||||
it('프로 키 활성화는 성공하고 tier 가 pro 가 된다', async () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
const result = await svc.activate(FX.LICENSE_PRO)
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.tier).toBe('pro')
|
||||
expect(svc.tier).toBe('pro')
|
||||
expect(svc.getInfo().licenseKey).toBe(FX.LICENSE_PRO)
|
||||
expect(svc.getInfo().activatedAt).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('플러스 키 활성화는 pro_plus 가 된다', async () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
const result = await svc.activate(FX.LICENSE_PLUS)
|
||||
expect(result.success).toBe(true)
|
||||
expect(svc.tier).toBe('pro_plus')
|
||||
})
|
||||
|
||||
it('비활성화하면 free 로 돌아간다', async () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
await svc.activate(FX.LICENSE_PRO)
|
||||
await svc.deactivate()
|
||||
expect(svc.tier).toBe('free')
|
||||
expect(svc.getInfo().licenseKey).toBeNull()
|
||||
})
|
||||
|
||||
it('로컬 받아쓰기는 익명 free 에서도 허용된다', () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
expect(svc.canUse(Feature.DICTATION).allowed).toBe(true)
|
||||
expect(svc.canUse(Feature.DICTATION).reason).toBe('ok')
|
||||
})
|
||||
|
||||
it('클라우드 PREMIUM_LLM 은 로컬 모드에서 login_required 이다', () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
const access = svc.canUse(Feature.PREMIUM_LLM)
|
||||
expect(access.allowed).toBe(false)
|
||||
expect(access.reason).toBe('login_required')
|
||||
})
|
||||
|
||||
it('클라우드 CLOUD_SYNC 도 로컬 모드에서 login_required 이다', () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
expect(svc.canUse(Feature.CLOUD_SYNC).reason).toBe('login_required')
|
||||
})
|
||||
|
||||
it('login_required 기능을 consumeQuota 하면 에러가 나고 upgrade-prompt 가 뜬다', () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
const prompts: unknown[] = []
|
||||
svc.on('upgrade-prompt', (e) => prompts.push(e))
|
||||
expect(() => svc.consumeQuota(Feature.PREMIUM_LLM)).toThrow()
|
||||
try {
|
||||
svc.consumeQuota(Feature.CLOUD_SYNC)
|
||||
} catch (err) {
|
||||
expect((err as { code: number }).code).toBe(ErrorCode.TierRequired)
|
||||
}
|
||||
expect(prompts.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('로컬 기능 consumeQuota 는 성공한다', () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
expect(() => svc.consumeQuota(Feature.DICTATION)).not.toThrow()
|
||||
})
|
||||
|
||||
it('쿼터 없는 기능 getUsage 는 무제한(-1) 이다', () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
const usage = svc.getUsage(Feature.DICTATION)
|
||||
expect(usage.limit).toBe(-1)
|
||||
expect(usage.remaining).toBe(-1)
|
||||
})
|
||||
|
||||
it('syncFromCloud 는 티어를 바꾸고 tier-changed 를 낸다', () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
const events: string[] = []
|
||||
svc.on('tier-changed', (info) => events.push(info.tier))
|
||||
svc.syncFromCloud('pro')
|
||||
expect(svc.tier).toBe('pro')
|
||||
expect(events).toContain('pro')
|
||||
})
|
||||
|
||||
it('같은 티어 syncFromCloud 는 이벤트를 내지 않는다', () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
let count = 0
|
||||
svc.on('tier-changed', () => {
|
||||
count += 1
|
||||
})
|
||||
svc.syncFromCloud('free')
|
||||
expect(count).toBe(0)
|
||||
})
|
||||
|
||||
it('resetToFree 는 pro 를 free 로 내린다', () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
svc.syncFromCloud('pro')
|
||||
svc.resetToFree()
|
||||
expect(svc.tier).toBe('free')
|
||||
})
|
||||
|
||||
it('getTierComparison 은 비어 있지 않다', () => {
|
||||
const svc = getLicenseService()
|
||||
svc.initialize()
|
||||
const rows = svc.getTierComparison()
|
||||
expect(rows.length).toBeGreaterThan(5)
|
||||
expect(rows.every((r) => r.featureLabel.length > 0)).toBe(true)
|
||||
})
|
||||
|
||||
it('IPC license:getInfo 는 free 를 돌려준다', async () => {
|
||||
getLicenseService().initialize()
|
||||
registerLicenseHandlers()
|
||||
const res = await invokeIpc(IPC_CHANNELS.LICENSE.GET_INFO)
|
||||
expect(res.success).toBe(true)
|
||||
if (res.success) expect(res.data.tier).toBe('free')
|
||||
})
|
||||
|
||||
it('IPC license:activate 잘못된 키는 성공 플래그가 false 다', async () => {
|
||||
getLicenseService().initialize()
|
||||
registerLicenseHandlers()
|
||||
const res = await invokeIpc(IPC_CHANNELS.LICENSE.ACTIVATE, { licenseKey: FX.LICENSE_BAD })
|
||||
expect(res.success).toBe(true)
|
||||
if (res.success) expect(res.data.success).toBe(false)
|
||||
})
|
||||
|
||||
it('IPC license:activate 프로 키 후 checkFeature dictation 은 허용', async () => {
|
||||
getLicenseService().initialize()
|
||||
registerLicenseHandlers()
|
||||
const act = await invokeIpc(IPC_CHANNELS.LICENSE.ACTIVATE, { licenseKey: FX.LICENSE_PRO })
|
||||
expect(act.success).toBe(true)
|
||||
const check = await invokeIpc(IPC_CHANNELS.LICENSE.CHECK_FEATURE, { feature: Feature.DICTATION })
|
||||
expect(check.success).toBe(true)
|
||||
if (check.success) expect(check.data.allowed).toBe(true)
|
||||
})
|
||||
|
||||
it('IPC license:deactivate 후 티어는 free', async () => {
|
||||
getLicenseService().initialize()
|
||||
registerLicenseHandlers()
|
||||
await invokeIpc(IPC_CHANNELS.LICENSE.ACTIVATE, { licenseKey: FX.LICENSE_PRO })
|
||||
const res = await invokeIpc(IPC_CHANNELS.LICENSE.DEACTIVATE)
|
||||
expect(res.success).toBe(true)
|
||||
expect(getLicenseService().tier).toBe('free')
|
||||
})
|
||||
|
||||
it('IPC license:getTierComparison 는 배열이다', async () => {
|
||||
getLicenseService().initialize()
|
||||
registerLicenseHandlers()
|
||||
const res = await invokeIpc(IPC_CHANNELS.LICENSE.GET_TIER_COMPARISON)
|
||||
expect(res.success).toBe(true)
|
||||
if (res.success) expect(Array.isArray(res.data)).toBe(true)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue