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:
Archer
2025-12-08 01:44:15 +08:00
committed by GitHub
parent 9d72f238c0
commit 2ccb5b50c6
247 changed files with 7342 additions and 3819 deletions

View File

@@ -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');
});
});