From aa025b0d46bf04ae9a8b0e0f1336bfc51b736ba1 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 7 Sep 2023 22:15:33 -0700 Subject: [PATCH] fix(server): storage usage should be float rather than int (#4275) --- .../server/src/modules/workspaces/resolver.ts | 2 +- apps/server/src/schema.gql | 2 +- apps/server/src/tests/app.e2e.ts | 34 +++++---- apps/server/src/tests/utils.ts | 32 +++++++- apps/server/src/tests/workspace-usage.spec.ts | 76 +++++++++++++++++++ 5 files changed, 127 insertions(+), 19 deletions(-) create mode 100644 apps/server/src/tests/workspace-usage.spec.ts diff --git a/apps/server/src/modules/workspaces/resolver.ts b/apps/server/src/modules/workspaces/resolver.ts index df63cf185b..b5937ab7ab 100644 --- a/apps/server/src/modules/workspaces/resolver.ts +++ b/apps/server/src/modules/workspaces/resolver.ts @@ -101,7 +101,7 @@ export class InvitationWorkspaceType { @ObjectType() export class WorkspaceBlobSizes { - @Field(() => Int) + @Field(() => Float) size!: number; } diff --git a/apps/server/src/schema.gql b/apps/server/src/schema.gql index 3deac9fa04..8f30b3b477 100644 --- a/apps/server/src/schema.gql +++ b/apps/server/src/schema.gql @@ -124,7 +124,7 @@ type InvitationWorkspaceType { } type WorkspaceBlobSizes { - size: Int! + size: Float! } type InvitationType { diff --git a/apps/server/src/tests/app.e2e.ts b/apps/server/src/tests/app.e2e.ts index c592cad9f8..d6ba99bcfb 100644 --- a/apps/server/src/tests/app.e2e.ts +++ b/apps/server/src/tests/app.e2e.ts @@ -6,7 +6,7 @@ import type { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { hashSync } from '@node-rs/argon2'; import { User } from '@prisma/client'; -import test from 'ava'; +import ava, { TestFn } from 'ava'; import { Express } from 'express'; // @ts-expect-error graphql-upload is not typed import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; @@ -17,7 +17,9 @@ import { PrismaService } from '../prisma/service'; const gql = '/graphql'; -let app: INestApplication; +const test = ava as TestFn<{ + app: INestApplication; +}>; class FakePrisma { fakeUser: User = { @@ -46,33 +48,33 @@ class FakePrisma { } } -test.beforeEach(async () => { +test.beforeEach(async t => { const module = await Test.createTestingModule({ imports: [AppModule], }) .overrideProvider(PrismaService) .useClass(FakePrisma) .compile(); - app = module.createNestApplication({ + t.context.app = module.createNestApplication({ cors: true, bodyParser: true, }); - app.use( + t.context.app.use( graphqlUploadExpress({ maxFileSize: 10 * 1024 * 1024, maxFiles: 5, }) ); - await app.init(); + await t.context.app.init(); }); -test.afterEach(async () => { - await app.close(); +test.afterEach(async t => { + await t.context.app.close(); }); test('should init app', async t => { - t.is(typeof app, 'object'); - await request(app.getHttpServer()) + t.is(typeof t.context.app, 'object'); + await request(t.context.app.getHttpServer()) .post(gql) .send({ query: ` @@ -83,9 +85,9 @@ test('should init app', async t => { }) .expect(400); - const { token } = await createToken(app); + const { token } = await createToken(t.context.app); - await request(app.getHttpServer()) + await request(t.context.app.getHttpServer()) .post(gql) .auth(token, { type: 'bearer' }) .send({ @@ -102,8 +104,8 @@ test('should init app', async t => { }); test('should find default user', async t => { - const { token } = await createToken(app); - await request(app.getHttpServer()) + const { token } = await createToken(t.context.app); + await request(t.context.app.getHttpServer()) .post(gql) .auth(token, { type: 'bearer' }) .send({ @@ -123,14 +125,14 @@ test('should find default user', async t => { }); test('should be able to upload avatar', async t => { - const { token, id } = await createToken(app); + const { token, id } = await createToken(t.context.app); const png = await Transformer.fromRgbaPixels( Buffer.alloc(400 * 400 * 4).fill(255), 400, 400 ).png(); - await request(app.getHttpServer()) + await request(t.context.app.getHttpServer()) .post(gql) .auth(token, { type: 'bearer' }) .field( diff --git a/apps/server/src/tests/utils.ts b/apps/server/src/tests/utils.ts index debd99b45c..4025a4e1a8 100644 --- a/apps/server/src/tests/utils.ts +++ b/apps/server/src/tests/utils.ts @@ -1,6 +1,9 @@ +import { randomUUID } from 'node:crypto'; + import type { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { PrismaClient } from '@prisma/client'; +import { hashSync } from '@node-rs/argon2'; +import { PrismaClient, User } from '@prisma/client'; // @ts-expect-error graphql-upload is not typed import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import request from 'supertest'; @@ -498,6 +501,33 @@ async function getInviteInfo( return res.body.data.getInviteInfo; } +export class FakePrisma { + fakeUser: User = { + id: randomUUID(), + name: 'Alex Yang', + avatarUrl: '', + email: 'alex.yang@example.org', + password: hashSync('123456'), + emailVerified: new Date(), + createdAt: new Date(), + }; + get user() { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const prisma = this; + return { + async findFirst() { + return prisma.fakeUser; + }, + async findUnique() { + return this.findFirst(); + }, + async update() { + return this.findFirst(); + }, + }; + } +} + export { acceptInvite, acceptInviteById, diff --git a/apps/server/src/tests/workspace-usage.spec.ts b/apps/server/src/tests/workspace-usage.spec.ts new file mode 100644 index 0000000000..b42aa859d8 --- /dev/null +++ b/apps/server/src/tests/workspace-usage.spec.ts @@ -0,0 +1,76 @@ +import type { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import ava, { TestFn } from 'ava'; +import { stub } from 'sinon'; + +import { AppModule } from '../app'; +import { UsersService } from '../modules/users'; +import { PermissionService } from '../modules/workspaces/permission'; +import { WorkspaceResolver } from '../modules/workspaces/resolver'; +import { PrismaService } from '../prisma'; +import { StorageProvide } from '../storage'; +import { FakePrisma } from './utils'; + +class FakePermission { + async tryCheck() { + return true; + } + async getWorkspaceOwner() { + return { + user: new FakePrisma().fakeUser, + }; + } +} + +const fakeUserService = { + getStorageQuotaById: stub(), +}; + +const test = ava as TestFn<{ + app: INestApplication; + resolver: WorkspaceResolver; +}>; + +test.beforeEach(async t => { + const module = await Test.createTestingModule({ + imports: [AppModule], + }) + .overrideProvider(PrismaService) + .useValue({ + userWorkspacePermission: { + async findMany() { + return []; + }, + }, + }) + .overrideProvider(PermissionService) + .useClass(FakePermission) + .overrideProvider(UsersService) + .useValue(fakeUserService) + .overrideProvider(StorageProvide) + .useValue({ + blobsSize() { + return 1024 * 10; + }, + }) + .compile(); + t.context.app = module.createNestApplication(); + t.context.resolver = t.context.app.get(WorkspaceResolver); + await t.context.app.init(); +}); + +test.afterEach(async t => { + await t.context.app.close(); +}); + +test('should get blob size limit', async t => { + const { resolver } = t.context; + fakeUserService.getStorageQuotaById.returns( + Promise.resolve(100 * 1024 * 1024 * 1024) + ); + const res = await resolver.checkBlobSize(new FakePrisma().fakeUser, '', 100); + t.not(res, false); + // @ts-expect-error + t.is(typeof res.size, 'number'); + fakeUserService.getStorageQuotaById.reset(); +});