Makelore 2.0 initial clean snapshot
This commit is contained in:
391
tests/unit/works-square.test.ts
Normal file
391
tests/unit/works-square.test.ts
Normal file
@@ -0,0 +1,391 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
createWorksProject,
|
||||
downloadWorksAssetToProject,
|
||||
fetchMyWorksProjectStatus,
|
||||
fetchMyWorksProjects,
|
||||
fetchWorksAsset,
|
||||
fetchWorksAssets,
|
||||
fetchWorksTokenUsage,
|
||||
fetchWorksProjectVersions,
|
||||
fetchWorksProjects,
|
||||
toPlazaCard,
|
||||
uploadWorksProjectZip,
|
||||
WorksSquareApiError,
|
||||
type ProjectPublic,
|
||||
} from '@/lib/works-square';
|
||||
|
||||
const hostApiFetchMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('@/lib/host-api', () => ({
|
||||
hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
|
||||
}));
|
||||
|
||||
describe('works square client', () => {
|
||||
beforeEach(() => {
|
||||
hostApiFetchMock.mockReset();
|
||||
});
|
||||
|
||||
it('loads public projects through the host api proxy', async () => {
|
||||
const page = {
|
||||
items: [
|
||||
{
|
||||
app_id: 'space-cleaner',
|
||||
title: '太空清洁队',
|
||||
summary: '收集漂浮垃圾的小游戏。',
|
||||
cover_url: null,
|
||||
category: 'game',
|
||||
age_band: '8-12',
|
||||
difficulty: 'beginner',
|
||||
updated_at: '2026-06-20T22:55:37.790408+08:00',
|
||||
},
|
||||
],
|
||||
next_cursor: null,
|
||||
limit: 12,
|
||||
};
|
||||
hostApiFetchMock.mockResolvedValueOnce({ success: true, page });
|
||||
|
||||
const result = await fetchWorksProjects({ q: 'space game', category: 'game', limit: 12 });
|
||||
|
||||
expect(result).toEqual(page);
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith(
|
||||
'/api/works/projects?q=space+game&category=game&limit=12',
|
||||
);
|
||||
});
|
||||
|
||||
it('loads public assets through the host api proxy', async () => {
|
||||
const page = {
|
||||
items: [
|
||||
{
|
||||
slug: 'tiny-farm',
|
||||
title: 'Tiny Farm',
|
||||
summary: 'Farm pixel resource package',
|
||||
category: '2D',
|
||||
source: 'Kenney',
|
||||
source_url: 'https://kenney.nl/assets/tiny-farm',
|
||||
license: 'CC0 1.0 Universal',
|
||||
license_url: 'https://creativecommons.org/publicdomain/zero/1.0/',
|
||||
series: 'Tiny',
|
||||
tags: ['farm', 'pixel'],
|
||||
preview_url: 'https://example.com/tiny-farm.png',
|
||||
image_urls: ['https://example.com/tiny-farm.png'],
|
||||
archive_size_bytes: 204800,
|
||||
extracted_file_count: 141,
|
||||
published_at: '2026-07-09T00:00:00Z',
|
||||
updated_at: '2026-07-09T08:30:00Z',
|
||||
},
|
||||
],
|
||||
next_cursor: null,
|
||||
limit: 12,
|
||||
};
|
||||
hostApiFetchMock.mockResolvedValueOnce({ success: true, assets: page });
|
||||
|
||||
const result = await fetchWorksAssets({
|
||||
q: 'farm pack',
|
||||
category: '2D',
|
||||
tag: 'pixel',
|
||||
source: 'Kenney',
|
||||
limit: 12,
|
||||
});
|
||||
|
||||
expect(result).toEqual(page);
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith(
|
||||
'/api/works/assets?q=farm+pack&category=2D&tag=pixel&source=Kenney&limit=12',
|
||||
);
|
||||
});
|
||||
|
||||
it('loads a public asset detail by slug', async () => {
|
||||
const asset = {
|
||||
slug: 'tiny-farm',
|
||||
title: 'Tiny Farm',
|
||||
summary: 'Farm pixel resource package',
|
||||
category: '2D',
|
||||
source: 'Kenney',
|
||||
tags: ['farm'],
|
||||
preview_url: null,
|
||||
image_urls: [],
|
||||
archive_size_bytes: null,
|
||||
extracted_file_count: null,
|
||||
published_at: null,
|
||||
updated_at: null,
|
||||
};
|
||||
hostApiFetchMock.mockResolvedValueOnce({ success: true, asset });
|
||||
|
||||
const result = await fetchWorksAsset('tiny farm');
|
||||
|
||||
expect(result).toEqual(asset);
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/assets/tiny%20farm');
|
||||
});
|
||||
|
||||
it('downloads a public asset into a selected project', async () => {
|
||||
const download = {
|
||||
slug: 'tiny-farm',
|
||||
filePath: 'D:/repo/game/assets/resource-square/tiny-farm.zip',
|
||||
relativePath: 'assets/resource-square/tiny-farm.zip',
|
||||
bytesWritten: 11,
|
||||
};
|
||||
hostApiFetchMock.mockResolvedValueOnce({ success: true, download });
|
||||
|
||||
const result = await downloadWorksAssetToProject({
|
||||
slug: 'tiny farm',
|
||||
projectId: 'prj_123',
|
||||
});
|
||||
|
||||
expect(result).toEqual(download);
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/assets/tiny%20farm/download', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ projectId: 'prj_123' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('creates project metadata with the current access token', async () => {
|
||||
const project = {
|
||||
app_id: 'space-cleaner',
|
||||
title: '太空清洁队',
|
||||
summary: '收集漂浮垃圾的小游戏。',
|
||||
cover_url: null,
|
||||
category: 'game',
|
||||
age_band: '8-12',
|
||||
difficulty: 'beginner',
|
||||
};
|
||||
hostApiFetchMock.mockResolvedValueOnce({ success: true, project });
|
||||
|
||||
await createWorksProject('access-token', project);
|
||||
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/projects', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ accessToken: 'access-token', project }),
|
||||
});
|
||||
});
|
||||
|
||||
it('throws api errors with upstream status for project metadata creation failures', async () => {
|
||||
hostApiFetchMock.mockResolvedValueOnce({
|
||||
success: false,
|
||||
status: 409,
|
||||
error: 'App ID already exists',
|
||||
});
|
||||
|
||||
await expect(createWorksProject('access-token', {
|
||||
app_id: 'space-cleaner',
|
||||
title: 'Space Cleaner',
|
||||
summary: 'Catch space trash',
|
||||
})).rejects.toMatchObject({
|
||||
name: 'WorksSquareApiError',
|
||||
statusCode: 409,
|
||||
message: 'App ID already exists',
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the current user projects with the current access token', async () => {
|
||||
const page = {
|
||||
items: [
|
||||
{
|
||||
app_id: 'space-cleaner',
|
||||
title: 'Space Cleaner',
|
||||
summary: 'Catch space trash',
|
||||
cover_url: null,
|
||||
category: 'game',
|
||||
age_band: '8-12',
|
||||
difficulty: 'beginner',
|
||||
status: 'draft',
|
||||
updated_at: '2026-06-20T22:55:37.790408+08:00',
|
||||
playable: false,
|
||||
runtime_url: null,
|
||||
},
|
||||
],
|
||||
next_cursor: null,
|
||||
limit: 50,
|
||||
};
|
||||
hostApiFetchMock.mockResolvedValueOnce({ success: true, page });
|
||||
|
||||
const result = await fetchMyWorksProjects('access-token', { limit: 50 });
|
||||
|
||||
expect(result).toEqual(page);
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith(
|
||||
'/api/works/projects/mine?limit=50',
|
||||
{
|
||||
headers: {
|
||||
'X-NianCode-Access-Token': 'access-token',
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('loads billing token usage with the current access token', async () => {
|
||||
const usage = {
|
||||
five_hour_remaining_percent: 72.5,
|
||||
weekly_remaining_percent: 48,
|
||||
plan_code: 'pro',
|
||||
plan_name: '专业版',
|
||||
};
|
||||
hostApiFetchMock.mockResolvedValueOnce({ success: true, usage });
|
||||
|
||||
const result = await fetchWorksTokenUsage('access-token');
|
||||
|
||||
expect(result).toEqual(usage);
|
||||
expect(result.plan_code).toBe('pro');
|
||||
expect(result.plan_name).toBe('专业版');
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith(
|
||||
'/api/works/billing/token-usage',
|
||||
{
|
||||
headers: {
|
||||
'X-NianCode-Access-Token': 'access-token',
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('loads the current user project status with versions', async () => {
|
||||
const status = {
|
||||
project: {
|
||||
app_id: 'space-cleaner',
|
||||
title: 'Space Cleaner',
|
||||
summary: 'Catch space trash',
|
||||
cover_url: null,
|
||||
category: 'game',
|
||||
age_band: '8-12',
|
||||
difficulty: 'beginner',
|
||||
status: 'draft',
|
||||
updated_at: '2026-06-20T22:55:37.790408+08:00',
|
||||
playable: false,
|
||||
runtime_url: null,
|
||||
},
|
||||
latest_version: {
|
||||
id: 'version-new',
|
||||
version_name: 'v1.1.0',
|
||||
review_status: 'building',
|
||||
change_log: 'Add score board',
|
||||
build_job_id: 'job-new',
|
||||
created_at: '2026-06-21T10:30:00+08:00',
|
||||
},
|
||||
versions: [],
|
||||
};
|
||||
hostApiFetchMock.mockResolvedValueOnce({ success: true, status });
|
||||
|
||||
const result = await fetchMyWorksProjectStatus('access-token', 'space cleaner');
|
||||
|
||||
expect(result).toEqual(status);
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith(
|
||||
'/api/works/projects/mine/space%20cleaner/status',
|
||||
{
|
||||
headers: {
|
||||
'X-NianCode-Access-Token': 'access-token',
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('exposes an api error class for status-based recovery', () => {
|
||||
const error = new WorksSquareApiError('Conflict', 409);
|
||||
|
||||
expect(error.name).toBe('WorksSquareApiError');
|
||||
expect(error.statusCode).toBe(409);
|
||||
expect(error.message).toBe('Conflict');
|
||||
});
|
||||
|
||||
it('uploads a project zip version with the current access token', async () => {
|
||||
hostApiFetchMock.mockResolvedValueOnce({
|
||||
success: true,
|
||||
upload: {
|
||||
version_id: 'b2f6f1dd-cf4b-4f03-a4b8-a71e2c8dd5f1',
|
||||
review_status: 'building',
|
||||
},
|
||||
});
|
||||
|
||||
const result = await uploadWorksProjectZip({
|
||||
accessToken: 'access-token',
|
||||
appId: 'space cleaner',
|
||||
projectId: 'project-1',
|
||||
versionName: 'v1.0.0',
|
||||
changeLog: 'Initial upload',
|
||||
zipFilePath: '/tmp/project.zip',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
version_id: 'b2f6f1dd-cf4b-4f03-a4b8-a71e2c8dd5f1',
|
||||
review_status: 'building',
|
||||
});
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith(
|
||||
'/api/works/projects/space%20cleaner/versions/upload',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
accessToken: 'access-token',
|
||||
projectId: 'project-1',
|
||||
versionName: 'v1.0.0',
|
||||
changeLog: 'Initial upload',
|
||||
zipFilePath: '/tmp/project.zip',
|
||||
}),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('loads uploaded versions with the current access token', async () => {
|
||||
hostApiFetchMock.mockResolvedValueOnce({
|
||||
success: true,
|
||||
versions: {
|
||||
items: [
|
||||
{
|
||||
id: 'ver_1',
|
||||
version_name: 'v1.0.0',
|
||||
review_status: 'building',
|
||||
change_log: 'First submitted version',
|
||||
build_job_id: 'job_1',
|
||||
created_at: '2026-06-21T10:30:00+08:00',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await fetchWorksProjectVersions('access-token', 'space cleaner');
|
||||
|
||||
expect(result).toEqual({
|
||||
items: [
|
||||
{
|
||||
id: 'ver_1',
|
||||
version_name: 'v1.0.0',
|
||||
review_status: 'building',
|
||||
change_log: 'First submitted version',
|
||||
build_job_id: 'job_1',
|
||||
created_at: '2026-06-21T10:30:00+08:00',
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith(
|
||||
'/api/works/projects/space%20cleaner/versions',
|
||||
{
|
||||
headers: {
|
||||
'X-NianCode-Access-Token': 'access-token',
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('maps api projects to gallery cards', () => {
|
||||
const project: ProjectPublic = {
|
||||
app_id: 'space-cleaner',
|
||||
title: '太空清洁队',
|
||||
summary: '收集漂浮垃圾的小游戏。',
|
||||
cover_url: 'https://example.com/cover.png',
|
||||
category: 'game',
|
||||
age_band: '8-12',
|
||||
difficulty: 'beginner',
|
||||
updated_at: '2026-06-20T22:55:37.790408+08:00',
|
||||
playable: true,
|
||||
runtime_url: '/apps/space-cleaner/',
|
||||
};
|
||||
|
||||
expect(toPlazaCard(project)).toEqual({
|
||||
id: 'space-cleaner',
|
||||
title: '太空清洁队',
|
||||
summary: '收集漂浮垃圾的小游戏。',
|
||||
coverImage: 'https://example.com/cover.png',
|
||||
type: 'game',
|
||||
ageBand: '8-12',
|
||||
difficulty: 'beginner',
|
||||
updatedAt: '2026-06-20T22:55:37.790408+08:00',
|
||||
playable: true,
|
||||
runtimeUrl: '/apps/space-cleaner/',
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user