refactor(server): use feature model (#9932)

This commit is contained in:
forehalo
2025-02-05 10:27:26 +00:00
parent 0ff8d3af6f
commit 7826e2b7c8
121 changed files with 1723 additions and 3826 deletions
@@ -0,0 +1,44 @@
# Snapshot report for `src/__tests__/models/feature-user.spec.ts`
The actual snapshot is saved in `feature-user.spec.ts.snap`.
Generated by [AVA](https://avajs.dev).
## should get user quota
> free plan
{
blobLimit: 10485760,
businessBlobLimit: 104857600,
copilotActionLimit: 10,
historyPeriod: 604800000,
memberLimit: 3,
name: 'Free',
storageQuota: 10737418240,
}
## should switch user quota
> switch to pro plan
{
blobLimit: 104857600,
copilotActionLimit: 10,
historyPeriod: 2592000000,
memberLimit: 10,
name: 'Pro',
storageQuota: 107374182400,
}
> switch to free plan
{
blobLimit: 10485760,
businessBlobLimit: 104857600,
copilotActionLimit: 10,
historyPeriod: 604800000,
memberLimit: 3,
name: 'Free',
storageQuota: 10737418240,
}
@@ -0,0 +1,18 @@
# Snapshot report for `src/__tests__/models/feature-workspace.spec.ts`
The actual snapshot is saved in `feature-workspace.spec.ts.snap`.
Generated by [AVA](https://avajs.dev).
## should get workspace quota
> team plan
{
blobLimit: 524288000,
historyPeriod: 2592000000,
memberLimit: 100,
name: 'Team Workspace',
seatQuota: 21474836480,
storageQuota: 2254857830400,
}
@@ -10,6 +10,7 @@ Generated by [AVA](https://avajs.dev).
{
blobLimit: 10485760,
businessBlobLimit: 104857600,
copilotActionLimit: 10,
historyPeriod: 604800000,
memberLimit: 3,
@@ -23,6 +24,7 @@ Generated by [AVA](https://avajs.dev).
{
blobLimit: 10485760,
businessBlobLimit: 104857600,
copilotActionLimit: 10,
historyPeriod: 604800000,
memberLimit: 3,
@@ -1,9 +1,8 @@
import { TestingModule } from '@nestjs/testing';
import { PrismaClient, User } from '@prisma/client';
import { User } from '@prisma/client';
import ava, { TestFn } from 'ava';
import { UserFeatureModel, UserModel } from '../../models';
import { createTestingModule, initTestingDB } from '../utils';
import { FeatureType, UserFeatureModel, UserModel } from '../../models';
import { createTestingModule, TestingModule } from '../utils';
interface Context {
module: TestingModule;
@@ -21,7 +20,7 @@ test.before(async t => {
});
test.beforeEach(async t => {
await initTestingDB(t.context.module.get(PrismaClient));
await t.context.module.initTestingDB();
t.context.u1 = await t.context.module.get(UserModel).create({
email: 'u1@affine.pro',
registered: true,
@@ -41,7 +40,13 @@ test('should get null if user feature not found', async t => {
test('should get user feature', async t => {
const { model, u1 } = t.context;
const userFeature = await model.get(u1.id, 'free_plan_v1');
t.is(userFeature?.feature, 'free_plan_v1');
t.is(userFeature?.name, 'free_plan_v1');
});
test('should get user quota', async t => {
const { model, u1 } = t.context;
const userQuota = await model.getQuota(u1.id);
t.snapshot(userQuota?.configs, 'free plan');
});
test('should list user features', async t => {
@@ -50,6 +55,16 @@ test('should list user features', async t => {
t.like(await model.list(u1.id), ['free_plan_v1']);
});
test('should list user features by type', async t => {
const { model, u1 } = t.context;
await model.add(u1.id, 'free_plan_v1', 'test');
await model.add(u1.id, 'unlimited_copilot', 'test');
t.like(await model.list(u1.id, FeatureType.Quota), ['free_plan_v1']);
t.like(await model.list(u1.id, FeatureType.Feature), ['unlimited_copilot']);
});
test('should directly test user feature existence', async t => {
const { model, u1 } = t.context;
@@ -82,14 +97,29 @@ test('should remove user feature', async t => {
t.false((await model.list(u1.id)).includes('free_plan_v1'));
});
test('should switch user feature', async t => {
test('should switch user quota', async t => {
const { model, u1 } = t.context;
await model.switch(u1.id, 'free_plan_v1', 'pro_plan_v1', 'test');
await model.switchQuota(u1.id, 'pro_plan_v1', 'test');
const quota = await model.getQuota(u1.id);
t.snapshot(quota?.configs, 'switch to pro plan');
t.false(await model.has(u1.id, 'free_plan_v1'));
t.true(await model.has(u1.id, 'pro_plan_v1'));
t.false((await model.list(u1.id)).includes('free_plan_v1'));
t.true((await model.list(u1.id)).includes('pro_plan_v1'));
await model.switchQuota(u1.id, 'free_plan_v1', 'test');
const quota2 = await model.getQuota(u1.id);
t.snapshot(quota2?.configs, 'switch to free plan');
});
test('should not switch user quota if the new quota is the same as the current one', async t => {
const { model, u1 } = t.context;
await model.switchQuota(u1.id, 'free_plan_v1', 'test not switch');
// @ts-expect-error private
const quota = await model.db.userFeature.findFirst({
where: {
userId: u1.id,
},
});
t.not(quota?.reason, 'test not switch');
});
@@ -1,9 +1,13 @@
import { TestingModule } from '@nestjs/testing';
import { PrismaClient, Workspace } from '@prisma/client';
import { Workspace } from '@prisma/client';
import ava, { TestFn } from 'ava';
import { UserModel, WorkspaceFeatureModel, WorkspaceModel } from '../../models';
import { createTestingModule, initTestingDB } from '../utils';
import {
FeatureType,
UserModel,
WorkspaceFeatureModel,
WorkspaceModel,
} from '../../models';
import { createTestingModule, type TestingModule } from '../utils';
interface Context {
module: TestingModule;
@@ -21,7 +25,7 @@ test.before(async t => {
});
test.beforeEach(async t => {
await initTestingDB(t.context.module.get(PrismaClient));
await t.context.module.initTestingDB();
const u1 = await t.context.module.get(UserModel).create({
email: 'u1@affine.pro',
registered: true,
@@ -46,18 +50,52 @@ test('should directly test workspace feature existence', async t => {
t.false(await model.has(ws.id, 'unlimited_workspace'));
});
test('should get workspace quota', async t => {
const { model, ws } = t.context;
await model.add(ws.id, 'team_plan_v1', 'test', {
memberLimit: 100,
});
const quota = await model.getQuota(ws.id);
t.snapshot(quota?.configs, 'team plan');
});
test('should return null if quota removed', async t => {
const { model, ws } = t.context;
await model.add(ws.id, 'team_plan_v1', 'test', {
memberLimit: 100,
});
await model.remove(ws.id, 'team_plan_v1');
const quota = await model.getQuota(ws.id);
t.is(quota, null);
});
test('should list empty workspace features', async t => {
const { model, ws } = t.context;
t.deepEqual(await model.list(ws.id), []);
});
test('should list workspace features by type', async t => {
const { model, ws } = t.context;
await model.add(ws.id, 'unlimited_workspace', 'test');
await model.add(ws.id, 'team_plan_v1', 'test');
t.like(await model.list(ws.id, FeatureType.Quota), ['team_plan_v1']);
t.like(await model.list(ws.id, FeatureType.Feature), ['unlimited_workspace']);
});
test('should add workspace feature', async t => {
const { model, ws } = t.context;
await model.add(ws.id, 'unlimited_workspace', 'test');
t.is(
(await model.get(ws.id, 'unlimited_workspace'))?.feature,
(await model.get(ws.id, 'unlimited_workspace'))?.name,
'unlimited_workspace'
);
t.true(await model.has(ws.id, 'unlimited_workspace'));
@@ -103,25 +141,3 @@ test('should remove workspace feature', async t => {
t.false(await model.has(ws.id, 'team_plan_v1'));
t.false((await model.list(ws.id)).includes('team_plan_v1'));
});
test('should switch workspace feature', async t => {
const { model, ws } = t.context;
await model.switch(ws.id, 'team_plan_v1', 'unlimited_workspace', 'test');
t.false(await model.has(ws.id, 'team_plan_v1'));
t.true(await model.has(ws.id, 'unlimited_workspace'));
t.false((await model.list(ws.id)).includes('team_plan_v1'));
t.true((await model.list(ws.id)).includes('unlimited_workspace'));
});
test('should switch workspace feature with overrides', async t => {
const { model, ws } = t.context;
await model.add(ws.id, 'unlimited_workspace', 'test');
await model.add(ws.id, 'team_plan_v1', 'test', { memberLimit: 100 });
const f2 = await model.get(ws.id, 'team_plan_v1');
t.is(f2!.configs.memberLimit, 100);
});
@@ -1,9 +1,8 @@
import { TestingModule } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import ava, { TestFn } from 'ava';
import { FeatureType } from '../../models';
import { FeatureModel } from '../../models/feature';
import { createTestingModule, initTestingDB } from '../utils';
import { createTestingModule, type TestingModule } from '../utils';
interface Context {
module: TestingModule;
@@ -20,7 +19,7 @@ test.before(async t => {
});
test.beforeEach(async t => {
await initTestingDB(t.context.module.get(PrismaClient));
await t.context.module.initTestingDB();
});
test.after(async t => {
@@ -91,7 +90,12 @@ test('should get feature if extra fields exist in feature config', async t => {
test('should create feature', async t => {
const { feature } = t.context;
const newFeature = await feature.upsert('new_feature' as any, {});
const newFeature = await feature.upsert(
'new_feature' as any,
{},
FeatureType.Feature,
1
);
t.deepEqual(newFeature.configs, {});
});
@@ -100,10 +104,15 @@ test('should update feature', async t => {
const { feature } = t.context;
const freePlanFeature = await feature.get('free_plan_v1');
const newFreePlanFeature = await feature.upsert('free_plan_v1', {
...freePlanFeature.configs,
memberLimit: 10,
});
const newFreePlanFeature = await feature.upsert(
'free_plan_v1',
{
...freePlanFeature.configs,
memberLimit: 10,
},
FeatureType.Quota,
1
);
t.deepEqual(newFreePlanFeature.configs, {
...freePlanFeature.configs,
@@ -113,7 +122,10 @@ test('should update feature', async t => {
test('should throw if feature config is invalid when updating', async t => {
const { feature } = t.context;
await t.throwsAsync(feature.upsert('free_plan_v1', {} as any), {
message: 'Invalid feature config for free_plan_v1',
});
await t.throwsAsync(
feature.upsert('free_plan_v1', {} as any, FeatureType.Quota, 1),
{
message: 'Invalid feature config for free_plan_v1',
}
);
});
@@ -1,4 +1,3 @@
import { TestingModule } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import ava, { TestFn } from 'ava';
@@ -8,7 +7,7 @@ import { PublicPageMode } from '../../models/common';
import { PageModel } from '../../models/page';
import { type User, UserModel } from '../../models/user';
import { type Workspace, WorkspaceModel } from '../../models/workspace';
import { createTestingModule, initTestingDB } from '../utils';
import { createTestingModule, type TestingModule } from '../utils';
interface Context {
config: Config;
@@ -36,7 +35,7 @@ let user: User;
let workspace: Workspace;
test.beforeEach(async t => {
await initTestingDB(t.context.db);
await t.context.module.initTestingDB();
user = await t.context.user.create({
email: 'test@affine.pro',
});
@@ -1,11 +1,10 @@
import { TestingModule } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import ava, { TestFn } from 'ava';
import { Config } from '../../base/config';
import { SessionModel } from '../../models/session';
import { UserModel } from '../../models/user';
import { createTestingModule, initTestingDB } from '../utils';
import { createTestingModule, type TestingModule } from '../utils';
interface Context {
config: Config;
@@ -28,7 +27,7 @@ test.before(async t => {
});
test.beforeEach(async t => {
await initTestingDB(t.context.db);
await t.context.module.initTestingDB();
});
test.after(async t => {
@@ -1,4 +1,3 @@
import { TestingModule } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import ava, { TestFn } from 'ava';
import Sinon from 'sinon';
@@ -7,7 +6,7 @@ import { EmailAlreadyUsed, EventBus } from '../../base';
import { WorkspaceRole } from '../../core/permission';
import { UserModel } from '../../models/user';
import { WorkspaceMemberStatus } from '../../models/workspace';
import { createTestingModule, initTestingDB } from '../utils';
import { createTestingModule, type TestingModule } from '../utils';
interface Context {
module: TestingModule;
@@ -24,7 +23,7 @@ test.before(async t => {
});
test.beforeEach(async t => {
await initTestingDB(t.context.module.get(PrismaClient));
await t.context.module.initTestingDB();
});
test.after(async t => {
@@ -1,4 +1,3 @@
import { TestingModule } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import ava, { TestFn } from 'ava';
@@ -6,7 +5,7 @@ import {
TokenType,
VerificationTokenModel,
} from '../../models/verification-token';
import { createTestingModule, initTestingDB } from '../utils';
import { createTestingModule, type TestingModule } from '../utils';
interface Context {
module: TestingModule;
@@ -25,7 +24,7 @@ test.before(async t => {
});
test.beforeEach(async t => {
await initTestingDB(t.context.db);
await t.context.module.initTestingDB();
});
test.after(async t => {
@@ -1,4 +1,3 @@
import { TestingModule } from '@nestjs/testing';
import { PrismaClient, WorkspaceMemberStatus } from '@prisma/client';
import ava, { TestFn } from 'ava';
import Sinon from 'sinon';
@@ -7,7 +6,7 @@ import { Config, EventBus } from '../../base';
import { WorkspaceRole } from '../../core/permission';
import { UserModel } from '../../models/user';
import { WorkspaceModel } from '../../models/workspace';
import { createTestingModule, initTestingDB } from '../utils';
import { createTestingModule, type TestingModule } from '../utils';
interface Context {
config: Config;
@@ -29,7 +28,7 @@ test.before(async t => {
});
test.beforeEach(async t => {
await initTestingDB(t.context.db);
await t.context.module.initTestingDB();
});
test.after(async t => {