打通一键发布与真机预览集成

需求:将登录续期、一键提交审核和精确 Release 真机预览合为可推进主分支的完整客户端链路。

实现:提交成功后保存安全版本映射,映射失败不误报上传失败;同步新会话测试契约和集成态项目文档。
This commit is contained in:
2026-08-08 18:06:46 +08:00
parent f02e99ea9b
commit 52626b332d
9 changed files with 144 additions and 52 deletions

View File

@@ -2159,9 +2159,11 @@ describe('OpencodeChatPanel', () => {
loading: false,
error: null,
accessToken: 'stale-token',
refreshToken: 'refresh-token',
tokenType: 'Bearer',
expiresAt: Date.now() + 60_000,
lastActiveAt: Date.now(),
canRefresh: true,
legacyRefreshToken: null,
user: {
username: 'student',
userId: '42',
@@ -2200,18 +2202,16 @@ describe('OpencodeChatPanel', () => {
},
};
}
if (path === '/api/auth/refresh' && init?.method === 'POST') {
expect(init.body).toBe(JSON.stringify({ refreshToken: 'refresh-token' }));
if (path === '/api/auth/session/refresh' && init?.method === 'POST') {
expect(init.body).toBe(JSON.stringify({ forceRefresh: true }));
return {
success: true,
token: {
access_token: 'fresh-token',
refresh_token: 'refresh-token',
token_type: 'Bearer',
expires_in: 3600,
username: 'student',
user_id: '42',
authorities: [],
session: {
accessToken: 'fresh-token',
tokenType: 'Bearer',
expiresAt: Date.now() + 3_600_000,
lastActiveAt: Date.now(),
canRefresh: true,
},
};
}

View File

@@ -1039,12 +1039,16 @@ describe('works square host api routes', () => {
}), { status: 201 }));
vi.stubGlobal('fetch', fetchMock);
const response = createResponse();
const recordSubmitted = vi.fn(async () => undefined);
const handled = await handleWorksRoutes(
createRequest('POST', { projectId: project.id, project: projectMetadata }),
response.res,
new URL('http://127.0.0.1/api/works/projects/publish-source'),
{ opencodeProjectStore: { listProjects: vi.fn(async () => [project]) } } as never,
{
opencodeProjectStore: { listProjects: vi.fn(async () => [project]) },
worksCloudDeployment: { recordSubmitted },
} as never,
);
expect(handled).toBe(true);
@@ -1100,6 +1104,53 @@ describe('works square host api routes', () => {
expect(archive).toBeInstanceOf(File);
expect((archive as File).name).toBe('project.zip');
expect((archive as File).size).toBeGreaterThan(0);
expect(recordSubmitted).toHaveBeenCalledWith(project.id, {
appId: 'space-cleaner',
versionId: 'version-1',
versionName: 'v2.3.4',
reviewStatus: 'building',
zipSha256: expect.stringMatching(/^[a-f0-9]{64}$/),
});
});
it('keeps a confirmed submission successful when the local preview mapping cannot be saved', async () => {
tempDir = await mkdtemp(join(tmpdir(), 'makelore-source-mapping-failure-'));
await writePublishableProject(tempDir);
const project = { id: 'project-1', path: tempDir, name: 'space-cleaner' };
const fetchMock = vi.fn()
.mockResolvedValueOnce(new Response('{}', { status: 201 }))
.mockResolvedValueOnce(new Response(JSON.stringify({
version_id: 'version-1',
review_status: 'building',
}), { status: 201 }));
vi.stubGlobal('fetch', fetchMock);
const response = createResponse();
await handleWorksRoutes(
createRequest('POST', {
projectId: project.id,
project: {
app_id: 'space-cleaner',
title: '太空清洁队',
summary: '收集漂浮垃圾的小游戏。',
},
}),
response.res,
new URL('http://127.0.0.1/api/works/projects/publish-source'),
{
opencodeProjectStore: { listProjects: vi.fn(async () => [project]) },
worksCloudDeployment: {
recordSubmitted: vi.fn(async () => { throw new Error('disk unavailable'); }),
},
} as never,
);
expect(response.statusCode).toBe(201);
expect(response.json()).toMatchObject({
success: true,
upload: { version_id: 'version-1', review_status: 'building' },
});
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it('uses a bounded timestamp version when VERSION.md is oversized', async () => {