Makelore 2.0 initial clean snapshot
This commit is contained in:
218
tests/unit/user-sync-store.test.ts
Normal file
218
tests/unit/user-sync-store.test.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { useOpencodeStore } from '@/stores/opencode';
|
||||
import { usePartnerProfilesStore } from '@/stores/partner-profiles';
|
||||
import { useProviderStore } from '@/stores/providers';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useUserSyncStore } from '@/stores/user-sync';
|
||||
|
||||
const hostApiFetchMock = vi.hoisted(() => vi.fn());
|
||||
const fetchProviderSnapshotMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('@/lib/host-api', () => ({
|
||||
hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
|
||||
ensureHostApiToken: vi.fn().mockResolvedValue('test-token'),
|
||||
createHostEventSource: vi.fn(() => ({ close: vi.fn() })),
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/provider-accounts', () => ({
|
||||
fetchProviderSnapshot: (...args: unknown[]) => fetchProviderSnapshotMock(...args),
|
||||
isHostApiRouteMissing: () => false,
|
||||
}));
|
||||
|
||||
function resetStores() {
|
||||
useAuthStore.setState({
|
||||
initialized: true,
|
||||
loading: false,
|
||||
error: null,
|
||||
authBase: 'https://biz.nianxx.cn/auth/',
|
||||
clientId: 'app',
|
||||
accessToken: 'access-token',
|
||||
refreshToken: 'refresh-token',
|
||||
tokenType: 'Bearer',
|
||||
expiresAt: Date.now() + 60_000,
|
||||
user: {
|
||||
username: 'zhangsan',
|
||||
userId: '1',
|
||||
tenantId: 7,
|
||||
deptId: 9,
|
||||
authorities: ['ROLE_USER'],
|
||||
},
|
||||
});
|
||||
useSettingsStore.setState({
|
||||
theme: 'system',
|
||||
language: 'en',
|
||||
sidebarCollapsed: false,
|
||||
devModeUnlocked: false,
|
||||
launchAtStartup: true,
|
||||
proxyServer: 'http://local-proxy.example.com',
|
||||
});
|
||||
usePartnerProfilesStore.setState({
|
||||
subagents: [],
|
||||
projectRuntimeConfigsByProjectId: {
|
||||
prj_1: {
|
||||
'nitu-pm-cloud': {
|
||||
filePath: 'D:/repo/.opencode/agent/nitu-pm-cloud.md',
|
||||
skillIds: ['pm-project-plan'],
|
||||
updatedAt: '2026-07-04T00:00:00.000Z',
|
||||
},
|
||||
},
|
||||
},
|
||||
profilesById: {},
|
||||
});
|
||||
useOpencodeStore.setState({
|
||||
activeProject: {
|
||||
id: 'prj_1',
|
||||
path: 'D:/repo',
|
||||
name: 'Course Project',
|
||||
createdAt: '2026-07-04T00:00:00.000Z',
|
||||
updatedAt: '2026-07-04T00:00:00.000Z',
|
||||
lastOpenedAt: '2026-07-04T00:00:00.000Z',
|
||||
},
|
||||
sessions: [{ id: 'sess_1', title: 'Local session' }],
|
||||
sessionsByProjectId: { prj_1: [{ id: 'sess_1', title: 'Local session' }] },
|
||||
selectedSessionId: 'sess_1',
|
||||
});
|
||||
useProviderStore.setState({
|
||||
statuses: [],
|
||||
accounts: [],
|
||||
vendors: [],
|
||||
defaultAccountId: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
useUserSyncStore.setState({
|
||||
loading: false,
|
||||
error: null,
|
||||
lastSyncedAt: null,
|
||||
});
|
||||
}
|
||||
|
||||
describe('useUserSyncStore', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
window.localStorage.clear();
|
||||
hostApiFetchMock.mockReset();
|
||||
fetchProviderSnapshotMock.mockResolvedValue({
|
||||
statuses: [],
|
||||
accounts: [],
|
||||
vendors: [],
|
||||
defaultAccountId: 'openai-account-1',
|
||||
});
|
||||
resetStores();
|
||||
});
|
||||
|
||||
it('hydrates user-level data while leaving project-owned stores untouched', async () => {
|
||||
hostApiFetchMock.mockResolvedValueOnce({
|
||||
success: true,
|
||||
bootstrap: {
|
||||
preferences: {
|
||||
theme: 'dark',
|
||||
language: 'zh-CN',
|
||||
sidebarCollapsed: true,
|
||||
launchAtStartup: false,
|
||||
activeProjectId: 'other-project',
|
||||
},
|
||||
providerAccounts: [{
|
||||
id: 'openai-account-1',
|
||||
vendorId: 'openai',
|
||||
label: 'OpenAI',
|
||||
authMode: 'api_key',
|
||||
enabled: true,
|
||||
isDefault: true,
|
||||
metadata: {},
|
||||
updatedAt: '2026-07-04T00:00:00.000Z',
|
||||
}],
|
||||
partners: [{
|
||||
id: 'nitu-pm-cloud',
|
||||
displayName: 'Cloud PM',
|
||||
templateRoleId: 'pm',
|
||||
description: 'Global cloud partner',
|
||||
skillIds: ['pm-project-plan'],
|
||||
filePath: 'D:/other/.opencode/agent/nitu-pm-cloud.md',
|
||||
projectRuntimeConfigsByProjectId: { other: { filePath: 'D:/other/file.md' } },
|
||||
createdAt: '2026-07-04T00:00:00.000Z',
|
||||
updatedAt: '2026-07-04T00:00:00.000Z',
|
||||
}],
|
||||
usageAggregates: [],
|
||||
projects: [{ id: 'other-project' }],
|
||||
},
|
||||
applied: { providerAccounts: 1 },
|
||||
});
|
||||
|
||||
await useUserSyncStore.getState().bootstrap();
|
||||
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/user-sync/bootstrap', {
|
||||
headers: { 'x-niancode-access-token': 'access-token' },
|
||||
});
|
||||
expect(useSettingsStore.getState().theme).toBe('dark');
|
||||
expect(useSettingsStore.getState().language).toBe('zh');
|
||||
expect(useSettingsStore.getState().sidebarCollapsed).toBe(true);
|
||||
expect(useSettingsStore.getState().launchAtStartup).toBe(true);
|
||||
expect(useSettingsStore.getState().proxyServer).toBe('http://local-proxy.example.com');
|
||||
expect(usePartnerProfilesStore.getState().subagents).toEqual([{
|
||||
id: 'nitu-pm-cloud',
|
||||
displayName: 'Cloud PM',
|
||||
templateRoleId: 'pm',
|
||||
description: 'Global cloud partner',
|
||||
avatarTone: undefined,
|
||||
avatarDataUrl: undefined,
|
||||
prompt: undefined,
|
||||
skillIds: ['pm-project-plan'],
|
||||
createdAt: '2026-07-04T00:00:00.000Z',
|
||||
updatedAt: '2026-07-04T00:00:00.000Z',
|
||||
}]);
|
||||
expect(usePartnerProfilesStore.getState().projectRuntimeConfigsByProjectId).toHaveProperty('prj_1');
|
||||
expect(useOpencodeStore.getState().activeProject?.id).toBe('prj_1');
|
||||
expect(useOpencodeStore.getState().sessions).toHaveLength(1);
|
||||
expect(fetchProviderSnapshotMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('pushes only safe preferences and excludes machine settings', async () => {
|
||||
hostApiFetchMock.mockResolvedValueOnce({
|
||||
success: true,
|
||||
bootstrap: { preferences: {}, providerAccounts: [], partners: [], usageAggregates: [] },
|
||||
});
|
||||
|
||||
await useUserSyncStore.getState().pushPreferences({
|
||||
theme: 'dark',
|
||||
language: 'zh-CN',
|
||||
launchAtStartup: true,
|
||||
proxyServer: 'http://proxy.example.com',
|
||||
} as never);
|
||||
|
||||
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/user-sync/preferences', {
|
||||
method: 'PUT',
|
||||
headers: { 'x-niancode-access-token': 'access-token' },
|
||||
body: JSON.stringify({ theme: 'dark', language: 'zh-CN' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('pushes partner records without project runtime mappings', async () => {
|
||||
hostApiFetchMock.mockResolvedValueOnce({
|
||||
success: true,
|
||||
bootstrap: { preferences: {}, providerAccounts: [], partners: [], usageAggregates: [] },
|
||||
});
|
||||
|
||||
await useUserSyncStore.getState().pushPartner({
|
||||
id: 'nitu-pm-test',
|
||||
displayName: 'PM',
|
||||
templateRoleId: 'pm',
|
||||
filePath: 'D:/repo/.opencode/agent/nitu-pm-test.md',
|
||||
skillIds: ['pm-project-plan'],
|
||||
createdAt: '2026-07-04T00:00:00.000Z',
|
||||
updatedAt: '2026-07-04T00:00:00.000Z',
|
||||
} as never);
|
||||
|
||||
const body = JSON.parse(String(hostApiFetchMock.mock.calls[0][1].body));
|
||||
expect(body).toEqual({
|
||||
id: 'nitu-pm-test',
|
||||
displayName: 'PM',
|
||||
templateRoleId: 'pm',
|
||||
skillIds: ['pm-project-plan'],
|
||||
createdAt: '2026-07-04T00:00:00.000Z',
|
||||
updatedAt: '2026-07-04T00:00:00.000Z',
|
||||
});
|
||||
expect(JSON.stringify(body)).not.toContain('filePath');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user