diff --git a/.eslintrc.js b/.eslintrc.js index e13090b43d..bce0ab722a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -272,6 +272,7 @@ const config = { ], '@typescript-eslint/no-floating-promises': 0, '@typescript-eslint/no-misused-promises': 0, + '@typescript-eslint/no-restricted-imports': 0, }, }, ], diff --git a/.github/workflows/build-server.yml b/.github/workflows/build-server.yml index c2d3441abf..d46b600784 100644 --- a/.github/workflows/build-server.yml +++ b/.github/workflows/build-server.yml @@ -118,7 +118,7 @@ jobs: CARGO_TARGET_DIR: '${{ github.workspace }}/target' DATABASE_URL: postgresql://affine:affine@localhost:5432/affine ENABLE_LOCAL_EMAIL: true - OAUTH_EMAIL_LOGIN: affine + OAUTH_EMAIL_LOGIN: noreply@toeverything.info OAUTH_EMAIL_PASSWORD: affine - name: Upload server test coverage results @@ -191,9 +191,6 @@ jobs: env: COVERAGE: true DATABASE_URL: postgresql://affine:affine@localhost:5432/affine - ENABLE_LOCAL_EMAIL: true - OAUTH_EMAIL_LOGIN: affine - OAUTH_EMAIL_PASSWORD: affine - name: Collect code coverage report run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov diff --git a/apps/core/src/components/affine/setting-modal/account-setting/index.tsx b/apps/core/src/components/affine/setting-modal/account-setting/index.tsx index 9942322eca..130fedcc90 100644 --- a/apps/core/src/components/affine/setting-modal/account-setting/index.tsx +++ b/apps/core/src/components/affine/setting-modal/account-setting/index.tsx @@ -44,7 +44,6 @@ export const UserAvatar = () => { const handleUpdateUserAvatar = useCallback( async (file: File) => { await avatarTrigger({ - id: user.id, avatar: file, }); // XXX: This is a hack to force the user to update, since next-auth can not only use update function without params diff --git a/apps/server/package.json b/apps/server/package.json index 057d4e8a2d..8d41f78bfa 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -81,6 +81,7 @@ "@types/cookie-parser": "^1.4.3", "@types/engine.io": "^3.1.7", "@types/express": "^4.17.17", + "@types/graphql-upload": "^16.0.1", "@types/keyv": "^4.2.0", "@types/lodash-es": "^4.17.9", "@types/node": "^18.17.12", @@ -104,19 +105,24 @@ }, "nodeArguments": [ "--loader", - "ts-node/esm.mjs", + "ts-node/esm/transpile-only.mjs", "--es-module-specifier-resolution", "node" ], "files": [ - "src/**/*.spec.ts" + "tests/**/*.spec.ts", + "tests/**/*.e2e.ts" ], "require": [ "./src/prelude.ts" ], "environmentVariables": { - "TS_NODE_PROJECT": "./tsconfig.json", - "NODE_ENV": "test" + "TS_NODE_PROJECT": "./tests/tsconfig.json", + "NODE_ENV": "test", + "ENABLE_LOCAL_EMAIL": "true", + "OAUTH_EMAIL_LOGIN": "noreply@toeverything.info", + "OAUTH_EMAIL_PASSWORD": "affine", + "OAUTH_EMAIL_SENDER": "noreply@toeverything.info" } }, "nodemonConfig": { diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 02e041bd86..4b83dbd446 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -19,7 +19,6 @@ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node'; import { PrismaInstrumentation } from '@prisma/instrumentation'; import cookieParser from 'cookie-parser'; import { static as staticMiddleware } from 'express'; -// @ts-expect-error graphql-upload is not typed import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import { AppModule } from './app'; diff --git a/apps/server/src/modules/auth/guard.ts b/apps/server/src/modules/auth/guard.ts index f2f31cbb35..6b4532354d 100644 --- a/apps/server/src/modules/auth/guard.ts +++ b/apps/server/src/modules/auth/guard.ts @@ -16,8 +16,7 @@ import { NextAuthOptionsProvide } from './next-auth-options'; import { AuthService } from './service'; export function getUserFromContext(context: ExecutionContext) { - const req = getRequestResponseFromContext(context).req; - return req.user; + return getRequestResponseFromContext(context).req.user; } /** diff --git a/apps/server/src/modules/users/resolver.ts b/apps/server/src/modules/users/resolver.ts index e9abdfd9e6..f7e629030c 100644 --- a/apps/server/src/modules/users/resolver.ts +++ b/apps/server/src/modules/users/resolver.ts @@ -15,7 +15,6 @@ import { Resolver, } from '@nestjs/graphql'; import type { User } from '@prisma/client'; -// @ts-expect-error graphql-upload is not typed import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'; import { PrismaService } from '../../prisma/service'; @@ -141,17 +140,16 @@ export class UserResolver { description: 'Upload user avatar', }) async uploadAvatar( - @Args('id') id: string, + @CurrentUser() user: UserType, @Args({ name: 'avatar', type: () => GraphQLUpload }) avatar: FileUpload ) { - const user = await this.users.findUserById(id); if (!user) { - throw new BadRequestException(`User ${id} not found`); + throw new BadRequestException(`User not found`); } - const url = await this.storage.uploadFile(`${id}-avatar`, avatar); + const url = await this.storage.uploadFile(`${user.id}-avatar`, avatar); return this.prisma.user.update({ - where: { id }, + where: { id: user.id }, data: { avatarUrl: url }, }); } diff --git a/apps/server/src/modules/workspaces/resolver.ts b/apps/server/src/modules/workspaces/resolver.ts index 43b3902d26..e83f2a97b5 100644 --- a/apps/server/src/modules/workspaces/resolver.ts +++ b/apps/server/src/modules/workspaces/resolver.ts @@ -26,7 +26,6 @@ import { Resolver, } from '@nestjs/graphql'; import type { User, Workspace } from '@prisma/client'; -// @ts-expect-error graphql-upload is not typed import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'; import { applyUpdate, Doc } from 'yjs'; @@ -648,10 +647,10 @@ export class WorkspaceResolver { workspaceId, }, }); - return ( - userWorkspace?.accepted && - (await this.permissions.grantPage(workspaceId, pageId)) - ); + if (!userWorkspace?.accepted) { + throw new ForbiddenException('Permission denied'); + } + return this.permissions.grantPage(workspaceId, pageId); } @Mutation(() => Boolean) diff --git a/apps/server/src/schema.gql b/apps/server/src/schema.gql index 1f70307a52..177f49b31c 100644 --- a/apps/server/src/schema.gql +++ b/apps/server/src/schema.gql @@ -190,7 +190,7 @@ type Mutation { deleteBlob(workspaceId: String!, hash: String!): Boolean! """Upload user avatar""" - uploadAvatar(id: String!, avatar: Upload!): UserType! + uploadAvatar(avatar: Upload!): UserType! """Remove user avatar""" removeAvatar: RemoveAvatar! diff --git a/apps/server/src/tests/app.e2e.ts b/apps/server/tests/app.e2e.ts similarity index 92% rename from apps/server/src/tests/app.e2e.ts rename to apps/server/tests/app.e2e.ts index 05763196b2..d5c713d530 100644 --- a/apps/server/src/tests/app.e2e.ts +++ b/apps/server/tests/app.e2e.ts @@ -8,12 +8,11 @@ import { hashSync } from '@node-rs/argon2'; import { User } from '@prisma/client'; import ava, { TestFn } from 'ava'; import { Express } from 'express'; -// @ts-expect-error graphql-upload is not typed import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import request from 'supertest'; -import { AppModule } from '../app'; -import { PrismaService } from '../prisma/service'; +import { AppModule } from '../src/app'; +import { PrismaService } from '../src/prisma/service'; const gql = '/graphql'; @@ -139,8 +138,8 @@ test('should be able to upload avatar and remove it', async t => { 'operations', JSON.stringify({ name: 'uploadAvatar', - query: `mutation uploadAvatar($id: String!, $avatar: Upload!) { - uploadAvatar(id: $id, avatar: $avatar) { + query: `mutation uploadAvatar($avatar: Upload!) { + uploadAvatar(avatar: $avatar) { id name avatarUrl @@ -166,17 +165,14 @@ test('should be able to upload avatar and remove it', async t => { query: ` mutation removeAvatar { removeAvatar { - id - name - avatarUrl - email + success } } `, }) .expect(200) .expect(res => { - t.is(res.body.data.removeAvatar.avatarUrl, ''); + t.is(res.body.data.removeAvatar.success, true); }); }); diff --git a/apps/server/src/tests/auth.e2e.ts b/apps/server/tests/auth.e2e.ts similarity index 94% rename from apps/server/src/tests/auth.e2e.ts rename to apps/server/tests/auth.e2e.ts index 4c2517e961..95a69f5735 100644 --- a/apps/server/src/tests/auth.e2e.ts +++ b/apps/server/tests/auth.e2e.ts @@ -1,18 +1,19 @@ +import { + getCurrentMailMessageCount, + getLatestMailMessage, +} from '@affine-test/kit/utils/cloud'; import type { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { PrismaClient } from '@prisma/client'; import ava, { TestFn } from 'ava'; -// @ts-expect-error graphql-upload is not typed import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; -import { AppModule } from '../app'; -import { MailService } from '../modules/auth/mailer'; -import { AuthService } from '../modules/auth/service'; +import { AppModule } from '../src/app'; +import { MailService } from '../src/modules/auth/mailer'; +import { AuthService } from '../src/modules/auth/service'; import { changeEmail, createWorkspace, - getCurrentMailMessageCount, - getLatestMailMessage, sendChangeEmail, sendVerifyChangeEmail, signUp, diff --git a/apps/server/src/tests/auth.spec.ts b/apps/server/tests/auth.spec.ts similarity index 89% rename from apps/server/src/tests/auth.spec.ts rename to apps/server/tests/auth.spec.ts index 93ea72ecbc..10a6a80d24 100644 --- a/apps/server/src/tests/auth.spec.ts +++ b/apps/server/tests/auth.spec.ts @@ -1,16 +1,16 @@ -/// +/// import { Test, TestingModule } from '@nestjs/testing'; import { PrismaClient } from '@prisma/client'; import test from 'ava'; -import { ConfigModule } from '../config'; -import { GqlModule } from '../graphql.module'; -import { MetricsModule } from '../metrics'; -import { AuthModule } from '../modules/auth'; -import { AuthResolver } from '../modules/auth/resolver'; -import { AuthService } from '../modules/auth/service'; -import { PrismaModule } from '../prisma'; -import { RateLimiterModule } from '../throttler'; +import { ConfigModule } from '../src/config'; +import { GqlModule } from '../src/graphql.module'; +import { MetricsModule } from '../src/metrics'; +import { AuthModule } from '../src/modules/auth'; +import { AuthResolver } from '../src/modules/auth/resolver'; +import { AuthService } from '../src/modules/auth/service'; +import { PrismaModule } from '../src/prisma'; +import { RateLimiterModule } from '../src/throttler'; let authService: AuthService; let authResolver: AuthResolver; diff --git a/apps/server/src/tests/config.spec.ts b/apps/server/tests/config.spec.ts similarity index 93% rename from apps/server/src/tests/config.spec.ts rename to apps/server/tests/config.spec.ts index e4c78a7f61..ad5c19138d 100644 --- a/apps/server/src/tests/config.spec.ts +++ b/apps/server/tests/config.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import test from 'ava'; -import { Config, ConfigModule } from '../config'; +import { Config, ConfigModule } from '../src/config'; let config: Config; let module: TestingModule; diff --git a/apps/server/src/tests/doc.spec.ts b/apps/server/tests/doc.spec.ts similarity index 93% rename from apps/server/src/tests/doc.spec.ts rename to apps/server/tests/doc.spec.ts index a48875f0b1..53e8fc8bef 100644 --- a/apps/server/src/tests/doc.spec.ts +++ b/apps/server/tests/doc.spec.ts @@ -7,10 +7,10 @@ import { register } from 'prom-client'; import * as Sinon from 'sinon'; import { Doc as YDoc, encodeStateAsUpdate } from 'yjs'; -import { Config, ConfigModule } from '../config'; -import { MetricsModule } from '../metrics'; -import { DocManager, DocModule } from '../modules/doc'; -import { PrismaModule, PrismaService } from '../prisma'; +import { Config, ConfigModule } from '../src/config'; +import { MetricsModule } from '../src/metrics'; +import { DocManager, DocModule } from '../src/modules/doc'; +import { PrismaModule, PrismaService } from '../src/prisma'; import { flushDB } from './utils'; const createModule = () => { diff --git a/apps/server/src/tests/exception-logger.e2e.ts b/apps/server/tests/exception-logger.e2e.ts similarity index 90% rename from apps/server/src/tests/exception-logger.e2e.ts rename to apps/server/tests/exception-logger.e2e.ts index ee798596e3..1180ae5c5d 100644 --- a/apps/server/src/tests/exception-logger.e2e.ts +++ b/apps/server/tests/exception-logger.e2e.ts @@ -1,13 +1,12 @@ import { Controller, Get, INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import test from 'ava'; -// @ts-expect-error graphql-upload is not typed import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import request from 'supertest'; -import { AppModule } from '../app'; -import { ExceptionLogger } from '../middleware/exception-logger'; -import { PrismaService } from '../prisma'; +import { AppModule } from '../src/app'; +import { ExceptionLogger } from '../src/middleware/exception-logger'; +import { PrismaService } from '../src/prisma'; const gql = '/graphql'; const rest = '/rest'; diff --git a/apps/server/src/tests/mailer.e2e.ts b/apps/server/tests/mailer.e2e.ts similarity index 53% rename from apps/server/src/tests/mailer.e2e.ts rename to apps/server/tests/mailer.e2e.ts index bdb1a501fe..6194b79691 100644 --- a/apps/server/src/tests/mailer.e2e.ts +++ b/apps/server/tests/mailer.e2e.ts @@ -1,4 +1,4 @@ -/// +/// // This test case is for testing the mailer service. // Please use local SMTP server for testing. // See: https://github.com/mailhog/MailHog @@ -8,38 +8,21 @@ import { } from '@affine-test/kit/utils/cloud'; import { Test, TestingModule } from '@nestjs/testing'; import { PrismaClient } from '@prisma/client'; -import test from 'ava'; +import ava, { TestFn } from 'ava'; -import { ConfigModule } from '../config'; -import { GqlModule } from '../graphql.module'; -import { MetricsModule } from '../metrics'; -import { AuthModule } from '../modules/auth'; -import { AuthService } from '../modules/auth/service'; -import { PrismaModule } from '../prisma'; -import { RateLimiterModule } from '../throttler'; +import { ConfigModule } from '../src/config'; +import { GqlModule } from '../src/graphql.module'; +import { MetricsModule } from '../src/metrics'; +import { AuthModule } from '../src/modules/auth'; +import { AuthService } from '../src/modules/auth/service'; +import { PrismaModule } from '../src/prisma'; +import { RateLimiterModule } from '../src/throttler'; -let auth: AuthService; -let module: TestingModule; -let skip = false; - -test.before(async () => { - try { - const response = await fetch('http://localhost:8025/api/v2/messages'); - if (!response.ok && !process.env.CI) { - console.warn('local mail not found, skip the mailer.e2e.ts'); - skip = true; - } - } catch (error) { - if ( - error instanceof Error && - (error.cause as any)?.code === 'ECONNREFUSED' && - !process.env.CI - ) { - console.warn('local mail not found, skip the mailer.e2e.ts'); - skip = true; - } - } -}); +const test = ava as TestFn<{ + auth: AuthService; + module: TestingModule; + skip: boolean; +}>; // cleanup database before each test test.beforeEach(async () => { @@ -48,8 +31,8 @@ test.beforeEach(async () => { await client.user.deleteMany({}); }); -test.beforeEach(async () => { - module = await Test.createTestingModule({ +test.beforeEach(async t => { + t.context.module = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ auth: { @@ -65,18 +48,16 @@ test.beforeEach(async () => { RateLimiterModule, ], }).compile(); - auth = module.get(AuthService); + t.context.auth = t.context.module.get(AuthService); }); -test.afterEach.always(async () => { - await module.close(); +test.afterEach.always(async t => { + await t.context.module.close(); }); test('should include callbackUrl in sending email', async t => { - if (skip) { - return t.pass(); - } - // await auth.signUp('Alex Yang', 'alexyang@example.org', '123456'); + const { auth } = t.context; + await auth.signUp('Alex Yang', 'alexyang@example.org', '123456'); for (const fn of [ 'sendSetPasswordEmail', 'sendChangeEmail', @@ -88,11 +69,10 @@ test('should include callbackUrl in sending email', async t => { const current = await getCurrentMailMessageCount(); const mail = await getLatestMailMessage(); t.regex( - mail.Content.Body, + mail?.Content?.Body, /https:\/\/test.com\/callback/, `should include callbackUrl when calling ${fn}` ); t.is(current, prev + 1, `calling ${fn}`); } - return; }); diff --git a/apps/server/src/tests/mailer.spec.ts b/apps/server/tests/mailer.spec.ts similarity index 84% rename from apps/server/src/tests/mailer.spec.ts rename to apps/server/tests/mailer.spec.ts index 19e82bbdb4..f85c6d9332 100644 --- a/apps/server/src/tests/mailer.spec.ts +++ b/apps/server/tests/mailer.spec.ts @@ -1,23 +1,17 @@ -import { ok } from 'node:assert'; import { randomUUID } from 'node:crypto'; 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'; -// @ts-expect-error graphql-upload is not typed +import ava, { TestFn } from 'ava'; import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; -import { AppModule } from '../app'; -import { MailService } from '../modules/auth/mailer'; -import { PrismaService } from '../prisma'; +import { AppModule } from '../src/app'; +import { MailService } from '../src/modules/auth/mailer'; +import { PrismaService } from '../src/prisma'; import { createWorkspace, getInviteInfo, inviteUser, signUp } from './utils'; -let app: INestApplication; - -let mail: MailService; - const FakePrisma = { fakeUser: { id: randomUUID(), @@ -95,14 +89,19 @@ const FakePrisma = { }, }; -test.beforeEach(async () => { +const test = ava as TestFn<{ + app: INestApplication; + mail: MailService; +}>; + +test.beforeEach(async t => { const module = await Test.createTestingModule({ imports: [AppModule], }) .overrideProvider(PrismaService) .useValue(FakePrisma) .compile(); - app = module.createNestApplication(); + const app = module.createNestApplication(); app.use( graphqlUploadExpress({ maxFileSize: 10 * 1024 * 1024, @@ -111,14 +110,17 @@ test.beforeEach(async () => { ); await app.init(); - mail = module.get(MailService); + const mail = module.get(MailService); + t.context.app = app; + t.context.mail = mail; }); -test.afterEach.always(async () => { - await app.close(); +test.afterEach.always(async t => { + await t.context.app.close(); }); test('should send invite email', async t => { + const { mail, app } = t.context; if (mail.hasConfigured()) { const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1'); const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1'); @@ -150,7 +152,7 @@ test('should send invite email', async t => { } ); - ok(resp.accepted.length === 1, 'failed to send invite email'); + t.is(resp.accepted.length, 1, 'failed to send invite email'); } t.pass(); }); diff --git a/apps/server/src/tests/prometheus-metrics.spec.ts b/apps/server/tests/prometheus-metrics.spec.ts similarity index 93% rename from apps/server/src/tests/prometheus-metrics.spec.ts rename to apps/server/tests/prometheus-metrics.spec.ts index a736d2fc43..c8fe384989 100644 --- a/apps/server/src/tests/prometheus-metrics.spec.ts +++ b/apps/server/tests/prometheus-metrics.spec.ts @@ -2,9 +2,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import test from 'ava'; import { register } from 'prom-client'; -import { MetricsModule } from '../metrics'; -import { Metrics } from '../metrics/metrics'; -import { PrismaModule } from '../prisma'; +import { MetricsModule } from '../src/metrics'; +import { Metrics } from '../src/metrics/metrics'; +import { PrismaModule } from '../src/prisma'; let metrics: Metrics; let module: TestingModule; diff --git a/apps/server/src/tests/session.spec.ts b/apps/server/tests/session.spec.ts similarity index 86% rename from apps/server/src/tests/session.spec.ts rename to apps/server/tests/session.spec.ts index bdb9a135d2..a344452d9f 100644 --- a/apps/server/src/tests/session.spec.ts +++ b/apps/server/tests/session.spec.ts @@ -1,10 +1,10 @@ -/// +/// import { Test, TestingModule } from '@nestjs/testing'; import ava, { TestFn } from 'ava'; -import { ConfigModule } from '../config'; -import { SessionModule, SessionService } from '../session'; +import { ConfigModule } from '../src/config'; +import { SessionModule, SessionService } from '../src/session'; const test = ava as TestFn<{ session: SessionService; diff --git a/apps/server/tests/tsconfig.json b/apps/server/tests/tsconfig.json new file mode 100644 index 0000000000..353667c1aa --- /dev/null +++ b/apps/server/tests/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "composite": true, + "target": "ESNext", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "rootDir": ".", + "outDir": "../dist/tests" + }, + "references": [ + { + "path": "../tsconfig.json" + }, + { + "path": "../../../tests/kit/tsconfig.json" + } + ], + "include": ["."], + "exclude": [] +} diff --git a/apps/server/src/tests/user.e2e.ts b/apps/server/tests/user.e2e.ts similarity index 96% rename from apps/server/src/tests/user.e2e.ts rename to apps/server/tests/user.e2e.ts index 403916991f..da06bc4322 100644 --- a/apps/server/src/tests/user.e2e.ts +++ b/apps/server/tests/user.e2e.ts @@ -2,11 +2,10 @@ import type { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { PrismaClient } from '@prisma/client'; import test from 'ava'; -// @ts-expect-error graphql-upload is not typed import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import request from 'supertest'; -import { AppModule } from '../app'; +import { AppModule } from '../src/app'; import { currentUser, signUp } from './utils'; let app: INestApplication; diff --git a/apps/server/src/tests/utils.ts b/apps/server/tests/utils.ts similarity index 98% rename from apps/server/src/tests/utils.ts rename to apps/server/tests/utils.ts index 827c10f003..5f2f5abf9a 100644 --- a/apps/server/src/tests/utils.ts +++ b/apps/server/tests/utils.ts @@ -5,9 +5,9 @@ import { hashSync } from '@node-rs/argon2'; import { PrismaClient, User } from '@prisma/client'; import request from 'supertest'; -import type { TokenType } from '../modules/auth'; -import type { UserType } from '../modules/users'; -import type { InvitationType, WorkspaceType } from '../modules/workspaces'; +import type { TokenType } from '../src/modules/auth'; +import type { UserType } from '../src/modules/users'; +import type { InvitationType, WorkspaceType } from '../src/modules/workspaces'; const gql = '/graphql'; diff --git a/apps/server/src/tests/workspace-blobs.spec.ts b/apps/server/tests/workspace-blobs.spec.ts similarity index 98% rename from apps/server/src/tests/workspace-blobs.spec.ts rename to apps/server/tests/workspace-blobs.spec.ts index 617b2e9a28..acd53ebe34 100644 --- a/apps/server/src/tests/workspace-blobs.spec.ts +++ b/apps/server/tests/workspace-blobs.spec.ts @@ -2,11 +2,10 @@ import type { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { PrismaClient } from '@prisma/client'; import test from 'ava'; -// @ts-expect-error graphql-upload is not typed import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import request from 'supertest'; -import { AppModule } from '../app'; +import { AppModule } from '../src/app'; import { checkBlobSize, collectAllBlobSizes, diff --git a/apps/server/src/tests/workspace-invite.e2e.ts b/apps/server/tests/workspace-invite.e2e.ts similarity index 94% rename from apps/server/src/tests/workspace-invite.e2e.ts rename to apps/server/tests/workspace-invite.e2e.ts index b38edce434..3dc21fb6de 100644 --- a/apps/server/src/tests/workspace-invite.e2e.ts +++ b/apps/server/tests/workspace-invite.e2e.ts @@ -6,12 +6,11 @@ import type { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { PrismaClient } from '@prisma/client'; import ava, { TestFn } from 'ava'; -// @ts-expect-error graphql-upload is not typed import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; -import { AppModule } from '../app'; -import { MailService } from '../modules/auth/mailer'; -import { AuthService } from '../modules/auth/service'; +import { AppModule } from '../src/app'; +import { MailService } from '../src/modules/auth/mailer'; +import { AuthService } from '../src/modules/auth/service'; import { acceptInvite, acceptInviteById, @@ -202,8 +201,7 @@ test('should send email', async t => { const inviteEmailContent = await getLatestMailMessage(); t.not( - // @ts-expect-error Third part library type mismatch - inviteEmailContent.To.find(item => { + inviteEmailContent.To.find((item: any) => { return item.Mailbox === 'production'; }), undefined, @@ -221,8 +219,7 @@ test('should send email', async t => { ); const acceptEmailContent = await getLatestMailMessage(); t.not( - // @ts-expect-error Third part library type mismatch - acceptEmailContent.To.find(item => { + acceptEmailContent.To.find((item: any) => { return item.Mailbox === 'u1'; }), undefined, @@ -239,8 +236,7 @@ test('should send email', async t => { ); const leaveEmailContent = await getLatestMailMessage(); t.not( - // @ts-expect-error Third part library type mismatch - leaveEmailContent.To.find(item => { + leaveEmailContent.To.find((item: any) => { return item.Mailbox === 'u1'; }), undefined, diff --git a/apps/server/src/tests/workspace-usage.spec.ts b/apps/server/tests/workspace-usage.spec.ts similarity index 83% rename from apps/server/src/tests/workspace-usage.spec.ts rename to apps/server/tests/workspace-usage.spec.ts index 17a426bbee..9526db2f28 100644 --- a/apps/server/src/tests/workspace-usage.spec.ts +++ b/apps/server/tests/workspace-usage.spec.ts @@ -3,12 +3,12 @@ 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 { AppModule } from '../src/app'; +import { UsersService } from '../src/modules/users'; +import { PermissionService } from '../src/modules/workspaces/permission'; +import { WorkspaceResolver } from '../src/modules/workspaces/resolver'; +import { PrismaService } from '../src/prisma'; +import { StorageProvide } from '../src/storage'; import { FakePrisma } from './utils'; class FakePermission { diff --git a/apps/server/src/tests/workspace.e2e.ts b/apps/server/tests/workspace.e2e.ts similarity index 97% rename from apps/server/src/tests/workspace.e2e.ts rename to apps/server/tests/workspace.e2e.ts index 83473528e0..1249d8dd8b 100644 --- a/apps/server/src/tests/workspace.e2e.ts +++ b/apps/server/tests/workspace.e2e.ts @@ -2,11 +2,10 @@ import type { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { PrismaClient } from '@prisma/client'; import ava, { TestFn } from 'ava'; -// @ts-expect-error graphql-upload is not typed import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import request from 'supertest'; -import { AppModule } from '../app'; +import { AppModule } from '../src/app'; import { acceptInvite, createWorkspace, @@ -133,7 +132,7 @@ test('should share a page', async t => { t.is(pages.length, 1, 'failed to get shared pages'); t.is(pages[0], 'page1', 'failed to get shared page: page1'); - const msg1 = await sharePage(app, u2.token.token, workspace.id, 'page2'); + const msg1 = await sharePage(app, u2.token.token, 'not_exists_ws', 'page2'); t.is(msg1, 'Permission denied', 'unauthorized user can share page'); const msg2 = await revokePage(app, u2.token.token, 'not_exists_ws', 'page2'); t.is(msg2, 'Permission denied', 'unauthorized user can share page'); diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index e41a2bba47..9cc86cac6d 100644 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -16,8 +16,8 @@ "verbatimModuleSyntax": false, "rootDir": "./src" }, - "include": ["./src", "package.json"], - "exclude": ["dist", "node_modules", "./src/tests"], + "include": ["src"], + "exclude": ["dist", "lib", "tests"], "references": [ { "path": "./tsconfig.node.json" diff --git a/apps/server/tsconfig.node.json b/apps/server/tsconfig.node.json index 15b75b25d8..82dc8124fe 100644 --- a/apps/server/tsconfig.node.json +++ b/apps/server/tsconfig.node.json @@ -18,5 +18,6 @@ "path": "../../tests/kit" } ], - "include": ["scripts", "package.json"] + "include": ["scripts", "package.json"], + "exclude": ["tests"] } diff --git a/packages/graphql/src/graphql/index.ts b/packages/graphql/src/graphql/index.ts index 88758d93d7..d59ef1bba2 100644 --- a/packages/graphql/src/graphql/index.ts +++ b/packages/graphql/src/graphql/index.ts @@ -497,8 +497,8 @@ export const uploadAvatarMutation = { definitionName: 'uploadAvatar', containsFile: true, query: ` -mutation uploadAvatar($id: String!, $avatar: Upload!) { - uploadAvatar(id: $id, avatar: $avatar) { +mutation uploadAvatar($avatar: Upload!) { + uploadAvatar(avatar: $avatar) { id name avatarUrl diff --git a/packages/graphql/src/graphql/upload-avatar.gql b/packages/graphql/src/graphql/upload-avatar.gql index 372187677a..1e08c21e9d 100644 --- a/packages/graphql/src/graphql/upload-avatar.gql +++ b/packages/graphql/src/graphql/upload-avatar.gql @@ -1,5 +1,5 @@ -mutation uploadAvatar($id: String!, $avatar: Upload!) { - uploadAvatar(id: $id, avatar: $avatar) { +mutation uploadAvatar($avatar: Upload!) { + uploadAvatar(avatar: $avatar) { id name avatarUrl diff --git a/packages/graphql/src/schema.ts b/packages/graphql/src/schema.ts index 8381dedbd9..20f0632227 100644 --- a/packages/graphql/src/schema.ts +++ b/packages/graphql/src/schema.ts @@ -441,7 +441,6 @@ export type SignUpMutation = { }; export type UploadAvatarMutationVariables = Exact<{ - id: Scalars['String']['input']; avatar: Scalars['Upload']['input']; }>; diff --git a/packages/i18n/tsconfig.node.json b/packages/i18n/tsconfig.node.json index 344e213d32..fa411df147 100644 --- a/packages/i18n/tsconfig.node.json +++ b/packages/i18n/tsconfig.node.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "types": ["node"] + "types": ["node"], + "outDir": "./lib/scripts" }, "include": ["./src/scripts"] } diff --git a/packages/workspace/src/affine/__tests__/gql.spec.tsx b/packages/workspace/src/affine/__tests__/gql.spec.tsx index 4bc16d9a1e..9bb659c1c8 100644 --- a/packages/workspace/src/affine/__tests__/gql.spec.tsx +++ b/packages/workspace/src/affine/__tests__/gql.spec.tsx @@ -136,7 +136,6 @@ describe('GraphQL wrapper for SWR', () => { mutation: uploadAvatarMutation, }); trigger({ - id: '1', avatar: new File([''], 'avatar.png'), }); } diff --git a/tests/kit/package.json b/tests/kit/package.json index 7ec0c6ffe6..43e563e8c6 100644 --- a/tests/kit/package.json +++ b/tests/kit/package.json @@ -1,6 +1,7 @@ { "name": "@affine-test/kit", "private": true, + "type": "module", "version": "0.9.0-canary.12", "exports": { "./playwright": "./playwright.ts", diff --git a/tsconfig.json b/tsconfig.json index 1f1b9668ac..4bc570294e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -96,6 +96,9 @@ { "path": "./apps/server" }, + { + "path": "./apps/server/tests" + }, { "path": "./apps/storybook" }, diff --git a/yarn.lock b/yarn.lock index c70ea4b47a..04869cab66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -690,6 +690,7 @@ __metadata: "@types/cookie-parser": ^1.4.3 "@types/engine.io": ^3.1.7 "@types/express": ^4.17.17 + "@types/graphql-upload": ^16.0.1 "@types/keyv": ^4.2.0 "@types/lodash-es": ^4.17.9 "@types/node": ^18.17.12 @@ -12695,6 +12696,15 @@ __metadata: languageName: node linkType: hard +"@types/accepts@npm:*": + version: 1.3.5 + resolution: "@types/accepts@npm:1.3.5" + dependencies: + "@types/node": "*" + checksum: 590b7580570534a640510c071e09074cf63b5958b237a728f94322567350aea4d239f8a9d897a12b15c856b992ee4d7907e9812bb079886af2c00714e7fb3f60 + languageName: node + linkType: hard + "@types/affine__env@workspace:*, @types/affine__env@workspace:packages/@types/env": version: 0.0.0-use.local resolution: "@types/affine__env@workspace:packages/@types/env" @@ -12841,6 +12851,13 @@ __metadata: languageName: node linkType: hard +"@types/content-disposition@npm:*": + version: 0.5.6 + resolution: "@types/content-disposition@npm:0.5.6" + checksum: da07798d52cc8fc46a8843d768b48d54c70f1a44c861dc2c73c4c25a1e08af859709629ab0e4d23d5198107b8926bb48c593df436ba68123d87191f5e25fe4bc + languageName: node + linkType: hard + "@types/cookie-parser@npm:^1.4.3": version: 1.4.3 resolution: "@types/cookie-parser@npm:1.4.3" @@ -12864,6 +12881,18 @@ __metadata: languageName: node linkType: hard +"@types/cookies@npm:*": + version: 0.7.8 + resolution: "@types/cookies@npm:0.7.8" + dependencies: + "@types/connect": "*" + "@types/express": "*" + "@types/keygrip": "*" + "@types/node": "*" + checksum: 7945b0cfe370bf1f05a1f328c9eba55333dac1bb9d7efa3148b107c260ab924263546351f9fd168daa72948d195464d395319a24477995f9f887a3a99fbcb5b5 + languageName: node + linkType: hard + "@types/cors@npm:^2.8.12": version: 2.8.13 resolution: "@types/cors@npm:2.8.13" @@ -13045,6 +13074,18 @@ __metadata: languageName: node linkType: hard +"@types/graphql-upload@npm:^16.0.1": + version: 16.0.1 + resolution: "@types/graphql-upload@npm:16.0.1" + dependencies: + "@types/express": "*" + "@types/koa": "*" + fs-capacitor: ^8.0.0 + graphql: 0.13.1 - 16 + checksum: 8ad2162f60f0808a6232cf2d2a8a7a10ce3e7a6a9cbb3bf80d8683a790b98b401d5ef69a83b8d011ce28ee67fdbb2ca1343051ea2133ec6a59772f94558967ba + languageName: node + linkType: hard + "@types/hast@npm:^2.0.0": version: 2.3.5 resolution: "@types/hast@npm:2.3.5" @@ -13061,6 +13102,13 @@ __metadata: languageName: node linkType: hard +"@types/http-assert@npm:*": + version: 1.5.3 + resolution: "@types/http-assert@npm:1.5.3" + checksum: 9553e5a0b8bcfdac4b51d3fa3b89a91b5450171861a667a5b4c47204e0f4a1ca865d97396e6ceaf220e87b64d06b7a8bad7bfba15ef97acb41a87507c9940dbc + languageName: node + linkType: hard + "@types/http-cache-semantics@npm:*": version: 4.0.1 resolution: "@types/http-cache-semantics@npm:4.0.1" @@ -13172,6 +13220,13 @@ __metadata: languageName: node linkType: hard +"@types/keygrip@npm:*": + version: 1.0.3 + resolution: "@types/keygrip@npm:1.0.3" + checksum: adee9a3efda3db9c64466af1c7c91a6d049420ee50589500cfd36e3e38d6abefdd858da88e6da63ed186e588127af3e862c1dc64fb0ad45c91870e6c35fe3be0 + languageName: node + linkType: hard + "@types/keyv@npm:^3.1.4": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -13190,6 +13245,31 @@ __metadata: languageName: node linkType: hard +"@types/koa-compose@npm:*": + version: 3.2.6 + resolution: "@types/koa-compose@npm:3.2.6" + dependencies: + "@types/koa": "*" + checksum: 1204c5bfa4c69448b692aba29c566ef6bedbdbe5842fa180450267a23d3606faa13ef209876fd0c989edb5bc381812a66610fcfeac196ce4e76364354756ba1f + languageName: node + linkType: hard + +"@types/koa@npm:*": + version: 2.13.9 + resolution: "@types/koa@npm:2.13.9" + dependencies: + "@types/accepts": "*" + "@types/content-disposition": "*" + "@types/cookies": "*" + "@types/http-assert": "*" + "@types/http-errors": "*" + "@types/keygrip": "*" + "@types/koa-compose": "*" + "@types/node": "*" + checksum: af9cd599c8e17e2ae0f4168a61d964e343f713d002b65fd995658d7addc6551ccadecfd32b3405cf44e4d360178ee4f972d6881533548261ae1f636a655d24b1 + languageName: node + linkType: hard + "@types/lodash-es@npm:^4.17.6, @types/lodash-es@npm:^4.17.9": version: 4.17.9 resolution: "@types/lodash-es@npm:4.17.9" @@ -21956,7 +22036,7 @@ __metadata: languageName: node linkType: hard -"graphql@npm:^15.0.0 || ^16.0.0, graphql@npm:^16.0.0, graphql@npm:^16.8.0": +"graphql@npm:0.13.1 - 16, graphql@npm:^15.0.0 || ^16.0.0, graphql@npm:^16.0.0, graphql@npm:^16.8.0": version: 16.8.0 resolution: "graphql@npm:16.8.0" checksum: d853d4085b0c911a7e2a926c3b0d379934ec61cd4329e70cdf281763102f024fd80a97db7a505b8b04fed9050cb4875f8f518150ea854557a500a0b41dcd7f4e