Files
Ncurator/tests/case/unit/release/app-update-publish-config.test.ts
T
hy d622985d55 refactor: update URLs and storage configurations to use domestic services
- Changed external links in settings-test.md and package.json to point to the new domestic website (guanzhangai.cn).
- Updated electron-builder.yml to publish updates from the new domestic URL.
- Introduced a new storage client for Tencent COS and refactored existing R2 client code to support both storage options.
- Implemented environment variable checks and configurations for Tencent COS and Cloudflare R2.
- Added tests for release storage configuration and ensured proper handling of live publish locks.
- Updated telemetry service to use the new official website URL for event reporting.
2026-09-01 11:44:56 +08:00

188 lines
9.5 KiB
TypeScript

import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
import { parse } from 'yaml'
import {
FREE_AI_KEYS_URL,
OFFICIAL_DOWNLOAD_PAGE_URL,
OFFICIAL_WEBSITE_URL,
USER_MANUAL_URL
} from '../../../../src/shared/constants'
type BuilderConfig = {
productName?: string
publish?: { provider?: string; url?: string }
win?: { executableName?: string }
nsis?: { include?: string; oneClick?: boolean; artifactName?: string }
mac?: { artifactName?: string }
linux?: unknown
}
const readBuilderConfig = async (): Promise<BuilderConfig> =>
parse(await readFile(path.resolve('electron-builder.yml'), 'utf8')) as BuilderConfig
describe('app update publish config', () => {
it('uses one domestic website base for every desktop website entry', async () => {
const [packageJsonText, telemetryService] = await Promise.all([
readFile(path.resolve('package.json'), 'utf8'),
readFile(path.resolve('src/main/services/telemetry/TelemetryService.ts'), 'utf8')
])
const packageJson = JSON.parse(packageJsonText) as { homepage?: string }
expect(OFFICIAL_WEBSITE_URL).toBe('https://www.guanzhangai.cn')
expect(USER_MANUAL_URL).toBe(`${OFFICIAL_WEBSITE_URL}/zh/manual`)
expect(FREE_AI_KEYS_URL).toBe(`${OFFICIAL_WEBSITE_URL}/zh/free-keys`)
expect(OFFICIAL_DOWNLOAD_PAGE_URL).toBe(`${OFFICIAL_WEBSITE_URL}/zh`)
expect(packageJson.homepage).toBe(OFFICIAL_WEBSITE_URL)
expect(telemetryService).toContain('`${OFFICIAL_WEBSITE_URL}/api/telemetry/events`')
})
it('uses the direct update feed and updater-aware NSIS behavior', async () => {
const [config, include] = await Promise.all([
readBuilderConfig(),
readFile(path.resolve('build/nsis-updated-silent.nsh'), 'utf8')
])
expect(config.publish).toEqual({
provider: 'generic',
url: 'https://download.guanzhangai.cn/updates/latest'
})
expect(config.nsis?.include).toBe('build/nsis-updated-silent.nsh')
expect(config.nsis?.oneClick).toBe(false)
expect(include).toContain('${if} ${isUpdated}')
expect(include).toContain('SetSilent silent')
expect(include).toContain('!insertmacro preventDowngrade HKCU')
expect(include).toContain('!insertmacro preventDowngrade HKLM')
expect(include).toContain('ReadRegStr $R0 ${ROOT_KEY}')
expect(include).toContain('${VersionCompare} "$R0" "${VERSION}" $R1')
expect(include).toContain('${if} $R1 == "1"')
expect(include).toContain('Quit')
})
it('uses the product brand for Windows user-visible files and stable macOS artifact names', async () => {
const config = await readBuilderConfig()
expect(config.productName).toBe('馆长')
expect(config.win?.executableName).toBe('馆长')
expect(config.nsis?.artifactName).toBe('馆长-${version}-Windows-${arch}.${ext}')
expect(config.mac?.artifactName).toBe('Ncurator-mac-${arch}.${ext}')
expect(config.linux).toBeUndefined()
})
it('runs the Windows pipeline on a local build backend only', async () => {
const [packageJsonText, windowsRelease, prepareRelease] = await Promise.all([
readFile(path.resolve('package.json'), 'utf8'),
readFile(path.resolve('scripts/release/windows.mjs'), 'utf8'),
readFile(path.resolve('scripts/release/prepare.mjs'), 'utf8')
])
const packageJson = JSON.parse(packageJsonText) as {
scripts: Record<string, string>
}
expect(packageJson.scripts['release:win']).toContain('release/windows.mjs')
expect(packageJson.scripts['release:win:auto']).toBeUndefined()
expect(windowsRelease).toContain('inspectLocalWindowsRelease')
expect(windowsRelease).not.toContain('dispatchWindowsRelease')
expect(windowsRelease).not.toContain('--actions')
expect(windowsRelease).not.toContain('--local-only')
expect(windowsRelease).not.toContain('--candidate-run-id')
expect(windowsRelease).not.toContain('--package-e2e')
expect(windowsRelease).toContain('readRegressionSelection')
expect(windowsRelease).not.toContain('--keep-old')
expect(windowsRelease).toContain('--prepare-only')
expect(windowsRelease).toContain('--test-only')
expect(windowsRelease).toContain('--publish-only')
expect(prepareRelease).toContain("'--prepare-only'")
expect(prepareRelease).toContain("valueAfter('--platform')")
expect(prepareRelease).toContain("['mac', 'windows']")
expect(prepareRelease).toContain('latestPlatformVersion')
expect(prepareRelease).toContain('prepared for independent macOS release')
expect(prepareRelease).toContain("'--exclude-group'")
expect(prepareRelease).toContain("'live'")
expect(prepareRelease).toContain("'typecheck:node'")
expect(prepareRelease).toContain("'typecheck:web'")
expect(prepareRelease).toContain('sqlite-migration-lock.mjs')
expect(prepareRelease).toContain("'--promote-current'")
expect(prepareRelease).not.toContain('test:update:e2e')
expect(prepareRelease).not.toContain('--package-e2e')
expect(prepareRelease).not.toContain('--actions')
})
it('finalizes Windows and macOS release channels independently with a live-publish lock', async () => {
const [windowsRelease, macRelease, windowsFinalize, macFinalize, tagContract, storageConfig] =
await Promise.all([
readFile(path.resolve('scripts/release/windows.mjs'), 'utf8'),
readFile(path.resolve('scripts/release/macos.mjs'), 'utf8'),
readFile(path.resolve('scripts/release/r2/finalize-windows.mjs'), 'utf8'),
readFile(path.resolve('scripts/release/r2/finalize-macos.mjs'), 'utf8'),
readFile(path.resolve('scripts/release/tag.mjs'), 'utf8'),
readFile(path.resolve('scripts/release/storage/config.mjs'), 'utf8')
])
expect(windowsRelease).toContain("'r2', 'finalize-windows.mjs'")
expect(macRelease).toContain("'r2', 'finalize-macos.mjs'")
expect(windowsFinalize).toContain('releases/latest/windows.json')
expect(windowsFinalize).not.toContain('releases/latest/macos.json')
expect(macFinalize).toContain('releases/latest/macos.json')
expect(macFinalize).not.toContain('releases/latest/windows.json')
expect(windowsFinalize).not.toContain('--keep-old')
expect(macFinalize).not.toContain('--keep-old')
expect(tagContract).toContain('/^(mac|windows)-v')
expect(windowsRelease).toContain('assertLivePublishAllowed')
expect(macRelease).toContain('assertLivePublishAllowed')
expect(storageConfig).toContain("RELEASE_STORAGE_TARGET || 'cos'")
expect(storageConfig).toContain('download.guanzhangai.cn')
})
it('shares source caches directly and warms packaged E2E before parallel workers', async () => {
const [testWorkflow, architectureValidator, warmupCase, macRelease] = await Promise.all([
readFile(path.resolve('scripts/test/workflow.mjs'), 'utf8'),
readFile(path.resolve('scripts/test/validate-test-architecture.mjs'), 'utf8'),
readFile(
path.resolve('tests/case/settings/workspace/files/workspace-prepare-refresh.e2e.test.ts'),
'utf8'
),
readFile(path.resolve('scripts/release/macos.mjs'), 'utf8')
])
expect(testWorkflow).toContain("task.execution === 'warmup'")
expect(testWorkflow).toContain("cacheMode: 'direct'")
expect(testWorkflow).toContain("cacheMode: 'warmup'")
expect(testWorkflow).toContain("task.execution !== 'exclusive' && task.execution !== 'warmup'")
expect(testWorkflow).toContain('VECTOR_WARMUP_TEST_FILE')
expect(testWorkflow).toContain('testFiles = [...testFiles, VECTOR_WARMUP_TEST_FILE]')
expect(testWorkflow).toContain("task.execution === 'warmup' ? '' : testNamePattern")
expect(testWorkflow).toContain('buildDirectKnowledgeCacheEnv')
expect(testWorkflow).toContain('NCURATOR_WORKSPACE_KNOWLEDGE_CACHE_STATS_PATH')
expect(testWorkflow).toContain('vector warm-up failed; parallel E2E will not start')
expect(testWorkflow).toContain('seedCaseKnowledgeCache')
expect(testWorkflow).toContain("path.basename(source) !== 'stats.json'")
expect(testWorkflow).toContain("new Map([['macos-x64', 'macos-arm64']])")
expect(testWorkflow).toContain("E2E_WORKERS_BY_TARGET = new Map([['macos-x64', 1]])")
expect(testWorkflow).toContain('const LIVE_WORKERS = 3')
expect(testWorkflow).not.toContain('LIVE_BACKEND_RETRIES')
expect(testWorkflow).not.toContain("extraArgs.push('--retry'")
expect(testWorkflow).toContain('parsed.caseIds.push(next)')
expect(testWorkflow).toContain('no test files match --case')
expect(testWorkflow).toContain('NCURATOR_TEST_RUNNER_DEADLINE_MS')
expect(testWorkflow).toContain('NCURATOR_E2E_CROSS_ARCH_WARMUP_SEED_PATH')
expect(testWorkflow).toContain('uses verified cross-architecture cache seed')
expect(testWorkflow).toContain('targetTests.every')
expect(macRelease).toContain("if (result.status === 'passed') status = 'passed'")
expect(macRelease).toContain("const DEFAULT_E2E_PLATFORMS = ['macos-arm64']")
expect(macRelease).toContain("args.includes('--include-x64-e2e')")
expect(macRelease).toContain("status: 'skipped'")
expect(testWorkflow).toContain("spawnSync('taskkill.exe'")
expect(testWorkflow).toContain("input.type === 'e2e' && input.target === 'package'")
expect(testWorkflow).toContain("prepareBetterSqliteNative('node')")
expect(architectureValidator).toContain('warmupFiles.length !== 1')
expect(warmupCase).toContain("execution: 'warmup'")
})
it('keeps LangSmith tracing opt-in by default', async () => {
const exampleEnv = await readFile(path.resolve('.env-example'), 'utf8')
expect(exampleEnv).toMatch(/^LANGSMITH_TRACING=false$/mu)
})
})