feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { setUserInfoContext } from '@coze-arch/logger';
import { refreshUserInfoBase, logoutBase, checkLoginBase } from '../factory';
import { type UserInfo } from '../../types';
import { useUserStore } from '../../store/user';
// Mock dependencies
vi.mock('@coze-arch/logger', () => ({
setUserInfoContext: vi.fn(),
}));
vi.mock('../../store/user', () => ({
useUserStore: {
getState: vi.fn(),
setState: vi.fn(),
},
}));
describe('factory.ts utility functions', () => {
let mockGetState: Mock;
let mockSetState: Mock;
let mockSetUserInfo: Mock;
let mockReset: Mock;
beforeEach(() => {
vi.clearAllMocks();
mockSetUserInfo = vi.fn();
mockReset = vi.fn();
mockGetState = useUserStore.getState as Mock;
mockSetState = useUserStore.setState as Mock;
mockGetState.mockReturnValue({
setUserInfo: mockSetUserInfo,
reset: mockReset,
});
});
describe('refreshUserInfoBase', () => {
it('should correctly refresh user information', async () => {
const mockUserInfo = {
user_id_str: '123',
name: 'Test User',
} as UserInfo;
const mockCheckLogin = vi.fn().mockResolvedValue(mockUserInfo);
await refreshUserInfoBase(mockCheckLogin);
expect(mockSetState).toHaveBeenCalledWith({ hasError: false });
expect(mockCheckLogin).toHaveBeenCalled();
expect(mockSetUserInfo).toHaveBeenCalledWith(mockUserInfo);
});
});
describe('logoutBase', () => {
it('should correctly execute logout operation', async () => {
const mockLogout = vi.fn().mockResolvedValue(undefined);
await logoutBase(mockLogout);
expect(mockLogout).toHaveBeenCalled();
expect(mockReset).toHaveBeenCalled();
});
});
describe('checkLoginBase', () => {
const mockSetUserInfoContext = setUserInfoContext as Mock;
it('should correctly handle successful login state', async () => {
const mockUserInfo = {
user_id_str: '123',
name: 'Test User',
} as UserInfo;
const mockCheckLoginImpl = vi
.fn()
.mockResolvedValue({ userInfo: mockUserInfo });
await checkLoginBase(mockCheckLoginImpl);
expect(mockSetState).toHaveBeenCalledWith({ hasError: false });
expect(mockSetUserInfoContext).toHaveBeenCalledWith(mockUserInfo);
expect(mockSetState).toHaveBeenCalledWith({
userInfo: mockUserInfo,
isSettled: true,
});
});
it('should correctly handle login error state', async () => {
const mockCheckLoginImpl = vi.fn().mockResolvedValue({ hasError: true });
await checkLoginBase(mockCheckLoginImpl);
expect(mockSetState).toHaveBeenCalledWith({ hasError: false });
expect(mockSetState).toHaveBeenCalledWith({ hasError: true });
expect(mockSetUserInfoContext).not.toHaveBeenCalled();
});
it('should correctly handle not logged in state', async () => {
const mockCheckLoginImpl = vi.fn().mockResolvedValue({ userInfo: null });
await checkLoginBase(mockCheckLoginImpl);
expect(mockSetState).toHaveBeenCalledWith({ hasError: false });
expect(mockSetState).toHaveBeenCalledWith({
userInfo: null,
isSettled: true,
});
expect(mockSetUserInfoContext).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,199 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { I18n } from '@coze-arch/i18n';
import {
getUserInfo,
getLoginStatus,
resetUserStore,
setUserInfo,
getUserLabel,
getUserAuthInfos,
subscribeUserAuthInfos,
usernameRegExpValidate,
} from '../index';
import { useUserStore } from '../../store/user';
// Mock useUserStore
vi.mock('../../store/user', () => ({
useUserStore: {
getState: vi.fn(),
setState: vi.fn(), // Though not directly used by all utils, good to have for setUserInfo
subscribe: vi.fn(),
},
}));
// Mock I18n
vi.mock('@coze-arch/i18n', () => ({
I18n: {
t: vi.fn(key => key), // Simple mock that returns the key
},
}));
describe('Utility functions from utils/index.ts', () => {
let mockGetState: Mock;
let mockSubscribe: Mock;
beforeEach(() => {
vi.clearAllMocks();
mockGetState = useUserStore.getState as Mock;
mockSubscribe = useUserStore.subscribe as Mock;
});
describe('getUserInfo', () => {
it('should return userInfo from userStore.getState()', () => {
const mockUser = { user_id_str: 'testUser', name: 'Test User' };
mockGetState.mockReturnValue({ userInfo: mockUser });
expect(getUserInfo()).toEqual(mockUser);
expect(mockGetState).toHaveBeenCalledTimes(1);
});
});
describe('getLoginStatus', () => {
it('should return "settling" if store is not settled', () => {
mockGetState.mockReturnValue({ isSettled: false, userInfo: null });
expect(getLoginStatus()).toBe('settling');
});
it('should return "not_login" if store is settled and no userInfo', () => {
mockGetState.mockReturnValue({ isSettled: true, userInfo: null });
expect(getLoginStatus()).toBe('not_login');
});
it('should return "not_login" if store is settled and userInfo has no user_id_str', () => {
mockGetState.mockReturnValue({
isSettled: true,
userInfo: { name: 'Test' },
});
expect(getLoginStatus()).toBe('not_login');
});
it('should return "logined" if store is settled and userInfo has user_id_str', () => {
mockGetState.mockReturnValue({
isSettled: true,
userInfo: { user_id_str: '123' },
});
expect(getLoginStatus()).toBe('logined');
});
});
describe('resetUserStore', () => {
it('should call reset on userStore.getState()', () => {
const mockReset = vi.fn();
mockGetState.mockReturnValue({ reset: mockReset });
resetUserStore();
expect(mockReset).toHaveBeenCalledTimes(1);
});
});
describe('setUserInfo', () => {
it('should call setUserInfo on userStore.getState() with the provided user info', () => {
const mockSetUserInfo = vi.fn();
mockGetState.mockReturnValue({ setUserInfo: mockSetUserInfo });
const newUser = { user_id_str: 'newUser', name: 'New User' };
setUserInfo(newUser);
expect(mockSetUserInfo).toHaveBeenCalledWith(newUser);
});
it('should call setUserInfo on userStore.getState() with null', () => {
const mockSetUserInfo = vi.fn();
mockGetState.mockReturnValue({ setUserInfo: mockSetUserInfo });
setUserInfo(null);
expect(mockSetUserInfo).toHaveBeenCalledWith(null);
});
});
describe('getUserLabel', () => {
it('should return userLabel from userStore.getState()', () => {
const mockLabel = { label_type: 1, text: 'VIP' };
mockGetState.mockReturnValue({ userLabel: mockLabel });
expect(getUserLabel()).toEqual(mockLabel);
expect(mockGetState).toHaveBeenCalledTimes(1);
});
});
describe('getUserAuthInfos', () => {
it('should call getUserAuthInfos on userStore.getState()', async () => {
const mockGetUserAuthInfos = vi.fn().mockResolvedValue([]);
mockGetState.mockReturnValue({ getUserAuthInfos: mockGetUserAuthInfos });
await getUserAuthInfos();
expect(mockGetUserAuthInfos).toHaveBeenCalledTimes(1);
});
});
describe('subscribeUserAuthInfos', () => {
it('should call userStore.subscribe with a selector for userAuthInfos and the callback', () => {
const callback = vi.fn();
const mockUserAuthInfos = [
{ auth_type: 'email', auth_key: 'test@example.com' },
];
// Mock the subscribe implementation to immediately call the listener with selected state
mockSubscribe.mockImplementation(
(selector, cb) => vi.fn(), // Return unsubscribe function
);
subscribeUserAuthInfos(callback);
expect(mockSubscribe).toHaveBeenCalledTimes(1);
// Check that the selector passed to subscribe correctly extracts userAuthInfos
const selectorArg = mockSubscribe.mock.calls[0][0];
expect(selectorArg({ userAuthInfos: mockUserAuthInfos })).toEqual(
mockUserAuthInfos,
);
expect(mockSubscribe.mock.calls[0][1]).toBe(callback);
});
});
describe('usernameRegExpValidate', () => {
it('should return null for valid usernames', () => {
expect(usernameRegExpValidate('validUser123')).toBeNull();
expect(usernameRegExpValidate('another_valid_user')).toBeNull();
expect(usernameRegExpValidate('USER')).toBeNull();
expect(usernameRegExpValidate('1234')).toBeNull();
});
it('should return "username_invalid_letter" for usernames with invalid characters', () => {
(I18n.t as Mock).mockReturnValueOnce('username_invalid_letter');
expect(usernameRegExpValidate('invalid-char')).toBe(
'username_invalid_letter',
);
(I18n.t as Mock).mockReturnValueOnce('username_invalid_letter');
expect(usernameRegExpValidate('invalid char')).toBe(
'username_invalid_letter',
);
(I18n.t as Mock).mockReturnValueOnce('username_invalid_letter');
expect(usernameRegExpValidate('!@#$%^')).toBe('username_invalid_letter');
expect(I18n.t).toHaveBeenCalledWith('username_invalid_letter');
});
it('should return "username_too_short" for usernames shorter than minLength (4)', () => {
(I18n.t as Mock).mockReturnValueOnce('username_too_short');
expect(usernameRegExpValidate('abc')).toBe('username_too_short');
(I18n.t as Mock).mockReturnValueOnce('username_too_short');
expect(usernameRegExpValidate('us')).toBe('username_too_short');
expect(I18n.t).toHaveBeenCalledWith('username_too_short');
});
it('should return "username_invalid_letter" if invalid char before checking length', () => {
// This case tests if the invalid character check takes precedence
(I18n.t as Mock).mockReturnValueOnce('username_invalid_letter');
expect(usernameRegExpValidate('a-b')).toBe('username_invalid_letter'); // Length 3, but invalid char
expect(I18n.t).toHaveBeenCalledWith('username_invalid_letter');
});
});
});

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { setUserInfoContext } from '@coze-arch/logger';
import { type UserInfo } from '../types';
import { useUserStore } from '../store/user';
/**
* 主动触发刷新用户信息
* @param checkLogin 登录检查函数
*/
export const refreshUserInfoBase = async (
checkLogin: () => Promise<UserInfo>,
) => {
useUserStore.setState({
hasError: false,
});
const userInfo = await checkLogin();
useUserStore.getState().setUserInfo(userInfo);
};
export const logoutBase = async (logout: () => Promise<void>) => {
await logout();
useUserStore.getState().reset();
};
export const checkLoginBase = async (
checkLoginImpl: () => Promise<{
userInfo?: UserInfo;
hasError?: boolean;
}>,
) => {
useUserStore.setState({
hasError: false,
});
const { userInfo, hasError } = await checkLoginImpl();
if (hasError) {
useUserStore.setState({
hasError: true,
});
return;
}
if (userInfo) {
setUserInfoContext(userInfo);
}
useUserStore.setState({
userInfo,
isSettled: true,
});
};

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type UserAuthInfo } from '@coze-arch/idl/developer_api';
import { I18n } from '@coze-arch/i18n';
import { type UserInfo, type LoginStatus } from '../types';
import { useUserStore } from '../store/user';
/**
* 获取用户信息
* @returns UserInfo 用户信息
*/
export const getUserInfo = () => useUserStore.getState().userInfo;
/**
* 获取登录状态
* @returns LoginStatus 登录状态
*/
export const getLoginStatus = (): LoginStatus => {
const state = useUserStore.getState();
if (state.isSettled) {
return state.userInfo?.user_id_str ? 'logined' : 'not_login';
}
return 'settling';
};
export const resetUserStore = () => useUserStore.getState().reset();
export const setUserInfo = (userInfo: UserInfo | null) =>
useUserStore.getState().setUserInfo(userInfo);
export const getUserLabel = () => useUserStore.getState().userLabel;
export const getUserAuthInfos = () =>
useUserStore.getState().getUserAuthInfos();
export const subscribeUserAuthInfos = (
callback: (state: UserAuthInfo[], prev: UserAuthInfo[]) => void,
) => useUserStore.subscribe(state => state.userAuthInfos, callback);
const usernameRegExp = /^[0-9A-Za-z_]+$/;
const minLength = 4;
export const usernameRegExpValidate = (value: string) => {
if (!usernameRegExp.exec(value)) {
return I18n.t('username_invalid_letter');
}
if (value.length < minLength) {
return I18n.t('username_too_short');
}
return null;
};