收敛客户端静态发布链路
This commit is contained in:
@@ -52,14 +52,14 @@ function createContext(deployment: Record<string, unknown> | null, activeProject
|
||||
path: 'D:/projects/planet-game',
|
||||
})),
|
||||
},
|
||||
worksCloudDeployment: {
|
||||
worksSubmissionBinding: {
|
||||
get: vi.fn(async () => deployment),
|
||||
},
|
||||
} as never;
|
||||
}
|
||||
|
||||
const submittedDeployment = {
|
||||
schema_version: 1,
|
||||
schema_version: 2,
|
||||
project_id: 'project-1',
|
||||
status: 'submitted',
|
||||
requested_at: '2026-08-03T01:00:00.000Z',
|
||||
@@ -78,7 +78,8 @@ function matchingRemotePayload(overrides: {
|
||||
project: {
|
||||
app_id: 'planet-game',
|
||||
playable: true,
|
||||
runtime_url: '/apps/planet-game/',
|
||||
play_url: '/apps/planet-game/',
|
||||
runtime_url: '/apps/legacy-planet-game/',
|
||||
version_name: 'v0.7.0',
|
||||
...overrides.project,
|
||||
},
|
||||
@@ -144,7 +145,39 @@ describe('device preview Host API route', () => {
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns only a trusted absolute HTTPS runtime URL for the submitted version', async () => {
|
||||
it('explains that a legacy automatic deployment must be resubmitted', async () => {
|
||||
const fetchMock = vi.fn();
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
const response = createResponse();
|
||||
|
||||
await handleDevicePreviewRoutes(
|
||||
createRequest(),
|
||||
response.res,
|
||||
new URL('http://127.0.0.1/api/opencode/projects/project-1/device-preview'),
|
||||
createContext({
|
||||
schema_version: 2,
|
||||
project_id: 'project-1',
|
||||
status: 'legacy_retired',
|
||||
requested_at: '2026-08-01T00:00:00.000Z',
|
||||
updated_at: '2026-08-10T00:00:00.000Z',
|
||||
error_code: 'LEGACY_AUTO_DEPLOY_RETIRED',
|
||||
message: '旧版自动部署任务已停用,请在项目配置中点击“提交审核”重新提交。',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.json()).toEqual({
|
||||
success: true,
|
||||
preview: {
|
||||
state: 'unavailable',
|
||||
projectId: 'project-1',
|
||||
updatedAt: '2026-08-10T00:00:00.000Z',
|
||||
message: '旧版自动部署任务已停用,请在项目配置中点击“提交审核”重新提交。',
|
||||
},
|
||||
});
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('prefers the trusted static play URL over the legacy runtime alias', async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValueOnce(new Response(
|
||||
JSON.stringify(matchingRemotePayload()),
|
||||
{ status: 200 },
|
||||
@@ -185,11 +218,35 @@ describe('device preview Host API route', () => {
|
||||
expect(JSON.stringify(response.json())).not.toContain('managed-secret-token');
|
||||
});
|
||||
|
||||
it('keeps the legacy runtime alias as a one-release fallback', async () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(new Response(
|
||||
JSON.stringify(matchingRemotePayload({
|
||||
project: { play_url: null, runtime_url: '/apps/runtime-fallback/' },
|
||||
})),
|
||||
{ status: 200 },
|
||||
)));
|
||||
const response = createResponse();
|
||||
|
||||
await handleDevicePreviewRoutes(
|
||||
createRequest(),
|
||||
response.res,
|
||||
new URL('http://127.0.0.1/api/opencode/projects/project-1/device-preview'),
|
||||
createContext(submittedDeployment),
|
||||
);
|
||||
|
||||
expect(response.json()).toMatchObject({
|
||||
preview: {
|
||||
state: 'ready',
|
||||
launchUrl: 'https://square.nianxx.cn/apps/runtime-fallback/',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates an exact short-lived owner preview for a pending-review release', async () => {
|
||||
const fetchMock = vi.fn()
|
||||
.mockResolvedValueOnce(new Response(
|
||||
JSON.stringify(matchingRemotePayload({
|
||||
project: { playable: false, runtime_url: null },
|
||||
project: { playable: false, play_url: null, runtime_url: null },
|
||||
latestVersion: { review_status: 'pending_review' },
|
||||
})),
|
||||
{ status: 200 },
|
||||
@@ -237,7 +294,7 @@ describe('device preview Host API route', () => {
|
||||
vi.stubGlobal('fetch', vi.fn()
|
||||
.mockResolvedValueOnce(new Response(
|
||||
JSON.stringify(matchingRemotePayload({
|
||||
project: { playable: false, runtime_url: null },
|
||||
project: { playable: false, play_url: null, runtime_url: null },
|
||||
latestVersion: { review_status: 'pending_review' },
|
||||
})),
|
||||
{ status: 200 },
|
||||
@@ -262,9 +319,9 @@ describe('device preview Host API route', () => {
|
||||
it.each([
|
||||
['an untrusted origin', 'https://evil.example/steal'],
|
||||
['an overlong URL', `https://square.nianxx.cn/apps/${'x'.repeat(1_100)}`],
|
||||
])('refuses a runtime URL from %s', async (_case, runtimeUrl) => {
|
||||
])('refuses a play URL from %s', async (_case, playUrl) => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(new Response(
|
||||
JSON.stringify(matchingRemotePayload({ project: { runtime_url: runtimeUrl } })),
|
||||
JSON.stringify(matchingRemotePayload({ project: { play_url: playUrl } })),
|
||||
{ status: 200 },
|
||||
)));
|
||||
const response = createResponse();
|
||||
@@ -445,7 +502,7 @@ describe('device preview Host API route', () => {
|
||||
const previewUrl = 'https://square.nianxx.cn/apps/planet-game/preview';
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(new Response(
|
||||
JSON.stringify(matchingRemotePayload({
|
||||
project: { runtime_url: undefined, preview_url: previewUrl },
|
||||
project: { play_url: undefined, runtime_url: undefined, preview_url: previewUrl },
|
||||
})),
|
||||
{ status: 200 },
|
||||
)));
|
||||
|
||||
Reference in New Issue
Block a user