mirror of
https://github.com/labring/FastGPT.git
synced 2026-01-17 01:04:06 +08:00
V4.14.4 features (#6036)
* feat: add query optimize and bill (#6021) * add query optimize and bill * perf: query extension * fix: embe model * remove log * remove log * fix: test --------- Co-authored-by: xxyyh <2289112474@qq> Co-authored-by: archer <545436317@qq.com> * feat: notice (#6013) * feat: record user's language * feat: notice points/dataset indexes; support count limit; update docker-compose.yml * fix: ts error * feat: send auth code i18n * chore: dataset notice limit * chore: adjust * fix: ts * fix: countLimit race condition; i18n en-prefix locale fallback to en --------- Co-authored-by: archer <545436317@qq.com> * perf: comment * perf: send inform code * fix: type error (#6029) * feat: add ip region for chat logs (#6010) * feat: add ip region for chat logs * refactor: use Geolite2.mmdb * fix: export chat logs * fix: return location directly * test: add unit test * perf: log show ip data * adjust commercial plans (#6008) * plan frontend * plan limit * coupon * discount coupon * fix * type * fix audit * type * plan name * legacy plan * track * feat: add discount coupon * fix * fix discount coupon * openapi * type * type * env * api type * fix * fix: simple agent plugin input & agent dashboard card (#6034) * refactor: remove gridfs (#6031) * fix: replace gridfs multer operations with s3 compatible ops * wip: s3 features * refactor: remove gridfs * fix * perf: mock test * doc * doc * doc * fix: test * fix: s3 * fix: mock s3 * remove invalid config * fix: init query extension * initv4144 (#6037) * chore: initv4144 * fix * version * fix: new plans (#6039) * fix: new plans * qr modal tip * fix: buffer raw text filename (#6040) * fix: initv4144 (#6041) * fix: pay refresh (#6042) * fix: migration shell * rename collection * clear timerlock * clear timerlock * perf: faq * perf: bill schema * fix: openapi * doc * fix: share var render * feat: delete dataset queue * plan usage display (#6043) * plan usage display * text * fix * fix: ts * perf: remove invalid code * perf: init shell * doc * perf: rename field * perf: avatar presign * init * custom plan text (#6045) * fix plans * fix * fixed * computed --------- Co-authored-by: archer <545436317@qq.com> * init shell * plan text & price page back button (#6046) * init * index * delete dataset * delete dataset * perf: delete dataset * init --------- Co-authored-by: YeYuheng <57035043+YYH211@users.noreply.github.com> Co-authored-by: xxyyh <2289112474@qq> Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: Roy <whoeverimf5@gmail.com> Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import * as loginApi from '@/pages/api/support/user/account/loginByPassword';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { UserStatusEnum } from '@fastgpt/global/support/user/constant';
|
||||
import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
import { authCode } from '@fastgpt/service/support/user/auth/controller';
|
||||
import { createUserSession } from '@fastgpt/service/support/user/session';
|
||||
import { setCookie } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||
import { addAuditLog } from '@fastgpt/service/support/user/audit/util';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
|
||||
import { Call } from '@test/utils/request';
|
||||
import type { PostLoginProps } from '@fastgpt/global/support/user/api.d';
|
||||
import { initTeamFreePlan } from '@fastgpt/service/support/wallet/sub/utils';
|
||||
|
||||
describe('loginByPassword API', () => {
|
||||
let testUser: any;
|
||||
let testTeam: any;
|
||||
let testTmb: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create test user and team
|
||||
testUser = await MongoUser.create({
|
||||
username: 'testuser',
|
||||
password: 'testpassword',
|
||||
status: UserStatusEnum.active
|
||||
});
|
||||
|
||||
testTeam = await MongoTeam.create({
|
||||
name: 'Test Team',
|
||||
ownerId: testUser._id
|
||||
});
|
||||
|
||||
await initTeamFreePlan({
|
||||
teamId: String(testTeam._id)
|
||||
});
|
||||
|
||||
testTmb = await MongoTeamMember.create({
|
||||
teamId: testTeam._id,
|
||||
userId: testUser._id,
|
||||
status: 'active',
|
||||
role: 'owner'
|
||||
});
|
||||
|
||||
await MongoUser.findByIdAndUpdate(testUser._id, {
|
||||
lastLoginTmbId: testTmb._id
|
||||
});
|
||||
|
||||
// Reset mocks before each test
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should login successfully with valid credentials', async () => {
|
||||
const res = await Call<PostLoginProps, {}, any>(loginApi.default, {
|
||||
body: {
|
||||
username: 'testuser',
|
||||
password: 'testpassword',
|
||||
code: '123456',
|
||||
language: 'zh-CN'
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(200);
|
||||
expect(res.error).toBeUndefined();
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.user).toBeDefined();
|
||||
expect(res.data.user.team).toBeDefined();
|
||||
expect(res.data.token).toBeDefined();
|
||||
expect(typeof res.data.token).toBe('string');
|
||||
expect(res.data.token.length).toBeGreaterThan(0);
|
||||
|
||||
// Verify authCode was called
|
||||
expect(authCode).toHaveBeenCalledWith({
|
||||
key: 'testuser',
|
||||
code: '123456',
|
||||
type: expect.any(String)
|
||||
});
|
||||
|
||||
// Verify setCookie was called
|
||||
expect(setCookie).toHaveBeenCalled();
|
||||
|
||||
// Verify tracking was called
|
||||
expect(pushTrack.login).toHaveBeenCalledWith({
|
||||
type: 'password',
|
||||
uid: testUser._id,
|
||||
teamId: String(testTeam._id),
|
||||
tmbId: String(testTmb._id)
|
||||
});
|
||||
|
||||
// Verify audit log was called
|
||||
expect(addAuditLog).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reject login when username is missing', async () => {
|
||||
const res = await Call<PostLoginProps, {}, any>(loginApi.default, {
|
||||
body: {
|
||||
username: '',
|
||||
password: 'testpassword',
|
||||
code: '123456'
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBe(CommonErrEnum.invalidParams);
|
||||
});
|
||||
|
||||
it('should reject login when password is missing', async () => {
|
||||
const res = await Call<PostLoginProps, {}, any>(loginApi.default, {
|
||||
body: {
|
||||
username: 'testuser',
|
||||
password: '',
|
||||
code: '123456'
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBe(CommonErrEnum.invalidParams);
|
||||
});
|
||||
|
||||
it('should reject login when code is missing', async () => {
|
||||
const res = await Call<PostLoginProps, {}, any>(loginApi.default, {
|
||||
body: {
|
||||
username: 'testuser',
|
||||
password: 'testpassword',
|
||||
code: ''
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBe(CommonErrEnum.invalidParams);
|
||||
});
|
||||
|
||||
it('should reject login when auth code verification fails', async () => {
|
||||
// Mock authCode to reject
|
||||
vi.mocked(authCode).mockRejectedValueOnce(new Error('Invalid code'));
|
||||
|
||||
const res = await Call<PostLoginProps, {}, any>(loginApi.default, {
|
||||
body: {
|
||||
username: 'testuser',
|
||||
password: 'testpassword',
|
||||
code: 'wrongcode'
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('should reject login when user does not exist', async () => {
|
||||
const res = await Call<PostLoginProps, {}, any>(loginApi.default, {
|
||||
body: {
|
||||
username: 'nonexistentuser',
|
||||
password: 'testpassword',
|
||||
code: '123456'
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBe(UserErrEnum.account_psw_error);
|
||||
});
|
||||
|
||||
it('should reject login when user is forbidden', async () => {
|
||||
// Update user status to forbidden
|
||||
await MongoUser.findByIdAndUpdate(testUser._id, {
|
||||
status: UserStatusEnum.forbidden
|
||||
});
|
||||
|
||||
const res = await Call<PostLoginProps, {}, any>(loginApi.default, {
|
||||
body: {
|
||||
username: 'testuser',
|
||||
password: 'testpassword',
|
||||
code: '123456'
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBe('Invalid account!');
|
||||
});
|
||||
|
||||
it('should reject login when password is incorrect', async () => {
|
||||
const res = await Call<PostLoginProps, {}, any>(loginApi.default, {
|
||||
body: {
|
||||
username: 'testuser',
|
||||
password: 'wrongpassword',
|
||||
code: '123456'
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBe(UserErrEnum.account_psw_error);
|
||||
});
|
||||
|
||||
it('should accept language parameter on successful login', async () => {
|
||||
// Spy on findByIdAndUpdate to verify it's called with the language
|
||||
const findByIdAndUpdateSpy = vi.spyOn(MongoUser, 'findByIdAndUpdate');
|
||||
|
||||
const res = await Call<PostLoginProps, {}, any>(loginApi.default, {
|
||||
body: {
|
||||
username: 'testuser',
|
||||
password: 'testpassword',
|
||||
code: '123456',
|
||||
language: 'en'
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(200);
|
||||
expect(res.error).toBeUndefined();
|
||||
|
||||
// Verify findByIdAndUpdate was called with the language
|
||||
expect(findByIdAndUpdateSpy).toHaveBeenCalledWith(
|
||||
testUser._id,
|
||||
expect.objectContaining({
|
||||
language: 'en'
|
||||
})
|
||||
);
|
||||
|
||||
findByIdAndUpdateSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle root user login correctly', async () => {
|
||||
// Create root user
|
||||
const rootUser = await MongoUser.create({
|
||||
username: 'root',
|
||||
password: 'rootpassword',
|
||||
status: UserStatusEnum.active
|
||||
});
|
||||
|
||||
const rootTeam = await MongoTeam.create({
|
||||
name: 'Root Team',
|
||||
ownerId: rootUser._id
|
||||
});
|
||||
|
||||
await initTeamFreePlan({
|
||||
teamId: String(rootTeam._id)
|
||||
});
|
||||
|
||||
const rootTmb = await MongoTeamMember.create({
|
||||
teamId: rootTeam._id,
|
||||
userId: rootUser._id,
|
||||
status: 'active',
|
||||
role: 'owner'
|
||||
});
|
||||
|
||||
await MongoUser.findByIdAndUpdate(rootUser._id, {
|
||||
lastLoginTmbId: rootTmb._id
|
||||
});
|
||||
|
||||
const res = await Call<PostLoginProps, {}, any>(loginApi.default, {
|
||||
body: {
|
||||
username: 'root',
|
||||
password: 'rootpassword',
|
||||
code: '123456'
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(200);
|
||||
expect(res.error).toBeUndefined();
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.token).toBeDefined();
|
||||
expect(typeof res.data.token).toBe('string');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user