Makelore 2.0 initial clean snapshot

This commit is contained in:
inman
2026-07-29 17:22:35 +08:00
commit b8ca3f8eea
694 changed files with 139782 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { AgentProfileApiError } from '@/lib/agent-profile';
import { buildAgentUserContext, isUserProfileComplete, useUserProfileStore } from '@/stores/user-profile';
const getAgentProfileMock = vi.hoisted(() => vi.fn());
const putAgentProfileMock = vi.hoisted(() => vi.fn());
vi.mock('@/lib/agent-profile', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/lib/agent-profile')>();
return {
...actual,
getAgentProfile: (...args: unknown[]) => getAgentProfileMock(...args),
putAgentProfile: (...args: unknown[]) => putAgentProfileMock(...args),
};
});
describe('global Agent user profile', () => {
beforeEach(() => {
getAgentProfileMock.mockReset();
putAgentProfileMock.mockReset();
useUserProfileStore.setState({ profilesByUserId: {}, syncStatesByUserId: {} });
});
it('stores profiles separately by account and shares every provided field', () => {
useUserProfileStore.getState().saveProfile('user-a', {
displayName: '小明', age: 16, gender: 'male',
});
useUserProfileStore.getState().saveProfile('user-b', {
displayName: '小红', age: null, gender: 'undisclosed',
});
const state = useUserProfileStore.getState();
expect(state.profilesByUserId['user-a']?.displayName).toBe('小明');
expect(state.profilesByUserId['user-b']?.displayName).toBe('小红');
expect(isUserProfileComplete(state.profilesByUserId['user-a'])).toBe(true);
const context = buildAgentUserContext(state.profilesByUserId['user-a']);
expect(context).toContain('用户名字是「小明」');
expect(context).toContain('用户年龄16 岁');
expect(context).toContain('用户性别:男');
});
it('uses the server profile as the source of truth when it already has a version', async () => {
getAgentProfileMock.mockResolvedValue({
display_name: '云端名字',
age: null,
gender: null,
share_age_with_agents: false,
share_gender_with_agents: false,
analysis_enabled: true,
completed: true,
version: 3,
updated_at: '2026-07-12T00:00:00Z',
});
useUserProfileStore.getState().saveProfile('user-a', {
displayName: '本地名字', age: 16, gender: 'male',
});
await useUserProfileStore.getState().syncProfile('user-a', 'access-token');
expect(useUserProfileStore.getState().profilesByUserId['user-a']).toMatchObject({
displayName: '云端名字',
age: null,
gender: 'undisclosed',
version: 3,
pendingSync: false,
});
expect(putAgentProfileMock).not.toHaveBeenCalled();
});
it('migrates a valid local profile only when the server is still at version zero', async () => {
getAgentProfileMock
.mockResolvedValueOnce({
display_name: null,
age: null,
gender: null,
share_age_with_agents: false,
share_gender_with_agents: false,
analysis_enabled: true,
completed: false,
version: 0,
updated_at: null,
})
.mockResolvedValueOnce({
display_name: '小明',
age: 16,
gender: 'male',
share_age_with_agents: true,
share_gender_with_agents: true,
analysis_enabled: true,
completed: true,
version: 1,
updated_at: '2026-07-12T00:00:00Z',
});
putAgentProfileMock.mockResolvedValue({
display_name: '小明',
age: 16,
gender: 'male',
share_age_with_agents: true,
share_gender_with_agents: true,
analysis_enabled: true,
completed: true,
version: 1,
updated_at: '2026-07-12T00:00:00Z',
});
useUserProfileStore.getState().saveProfile('user-a', {
displayName: '小明', age: 16, gender: 'male',
});
await useUserProfileStore.getState().syncProfile('user-a', 'access-token');
expect(putAgentProfileMock).toHaveBeenCalledWith('access-token', {
display_name: '小明',
age: 16,
gender: 'male',
share_age_with_agents: true,
share_gender_with_agents: true,
analysis_enabled: true,
version: 0,
});
expect(getAgentProfileMock).toHaveBeenCalledTimes(2);
expect(useUserProfileStore.getState().profilesByUserId['user-a']?.version).toBe(1);
});
it('saves edits with the current server version', async () => {
putAgentProfileMock.mockResolvedValue({
display_name: '更新后',
age: null,
gender: null,
share_age_with_agents: false,
share_gender_with_agents: false,
analysis_enabled: true,
completed: true,
version: 4,
updated_at: '2026-07-12T00:00:00Z',
});
useUserProfileStore.getState().saveProfile('user-a', {
displayName: '更新后', age: null, gender: 'undisclosed',
});
useUserProfileStore.setState((state) => ({
profilesByUserId: {
...state.profilesByUserId,
'user-a': { ...state.profilesByUserId['user-a'], version: 3 },
},
}));
await useUserProfileStore.getState().saveProfileToServer('user-a', 'access-token');
expect(putAgentProfileMock).toHaveBeenCalledWith('access-token', {
display_name: '更新后',
age: null,
gender: null,
share_age_with_agents: false,
share_gender_with_agents: false,
analysis_enabled: true,
version: 3,
});
expect(useUserProfileStore.getState().profilesByUserId['user-a']?.version).toBe(4);
});
it('reloads the server profile after a version conflict instead of retrying the old edit', async () => {
putAgentProfileMock.mockRejectedValue(new AgentProfileApiError(409, 'Profile version conflict'));
getAgentProfileMock.mockResolvedValue({
display_name: '其他设备的名字',
age: 18,
gender: 'female',
share_age_with_agents: false,
share_gender_with_agents: false,
analysis_enabled: true,
completed: true,
version: 5,
updated_at: '2026-07-12T00:00:00Z',
});
useUserProfileStore.getState().saveProfile('user-a', {
displayName: '本地编辑', age: null, gender: 'undisclosed',
});
useUserProfileStore.setState((state) => ({
profilesByUserId: {
...state.profilesByUserId,
'user-a': { ...state.profilesByUserId['user-a'], version: 4 },
},
}));
await expect(useUserProfileStore.getState().saveProfileToServer('user-a', 'access-token'))
.rejects.toMatchObject({ status: 409 });
expect(getAgentProfileMock).toHaveBeenCalledWith('access-token');
expect(useUserProfileStore.getState().profilesByUserId['user-a']).toMatchObject({
displayName: '其他设备的名字',
version: 5,
pendingSync: false,
});
expect(putAgentProfileMock).toHaveBeenCalledTimes(1);
});
});