2024-04-30 06:46:59 +03:00
|
|
|
import { Readable } from 'node:stream';
|
|
|
|
|
|
|
|
import { HttpStatus, INestApplication } from '@nestjs/common';
|
2024-12-09 12:51:54 +03:00
|
|
|
import { PrismaClient, WorkspaceMemberStatus } from '@prisma/client';
|
2024-04-30 06:46:59 +03:00
|
|
|
import ava, { TestFn } from 'ava';
|
|
|
|
import Sinon from 'sinon';
|
|
|
|
import request from 'supertest';
|
|
|
|
|
|
|
|
import { AppModule } from '../../src/app.module';
|
|
|
|
import { CurrentUser } from '../../src/core/auth';
|
|
|
|
import { AuthService } from '../../src/core/auth/service';
|
2024-08-21 08:30:26 +03:00
|
|
|
import { PgWorkspaceDocStorageAdapter } from '../../src/core/doc';
|
2024-04-30 06:46:59 +03:00
|
|
|
import { WorkspaceBlobStorage } from '../../src/core/storage';
|
|
|
|
import { createTestingApp, internalSignIn } from '../utils';
|
|
|
|
|
|
|
|
const test = ava as TestFn<{
|
|
|
|
u1: CurrentUser;
|
|
|
|
db: PrismaClient;
|
|
|
|
app: INestApplication;
|
|
|
|
storage: Sinon.SinonStubbedInstance<WorkspaceBlobStorage>;
|
2024-08-21 08:30:26 +03:00
|
|
|
workspace: Sinon.SinonStubbedInstance<PgWorkspaceDocStorageAdapter>;
|
2024-04-30 06:46:59 +03:00
|
|
|
}>;
|
|
|
|
|
2024-08-21 08:30:26 +03:00
|
|
|
test.before(async t => {
|
2024-04-30 06:46:59 +03:00
|
|
|
const { app } = await createTestingApp({
|
|
|
|
imports: [AppModule],
|
|
|
|
tapModule: m => {
|
|
|
|
m.overrideProvider(WorkspaceBlobStorage)
|
|
|
|
.useValue(Sinon.createStubInstance(WorkspaceBlobStorage))
|
2024-08-21 08:30:26 +03:00
|
|
|
.overrideProvider(PgWorkspaceDocStorageAdapter)
|
|
|
|
.useValue(Sinon.createStubInstance(PgWorkspaceDocStorageAdapter));
|
2024-04-30 06:46:59 +03:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const auth = app.get(AuthService);
|
2024-09-03 12:03:39 +03:00
|
|
|
t.context.u1 = await auth.signUp('u1@affine.pro', '1');
|
2024-04-30 06:46:59 +03:00
|
|
|
const db = app.get(PrismaClient);
|
|
|
|
|
|
|
|
t.context.db = db;
|
|
|
|
t.context.app = app;
|
|
|
|
t.context.storage = app.get(WorkspaceBlobStorage);
|
2024-08-21 08:30:26 +03:00
|
|
|
t.context.workspace = app.get(PgWorkspaceDocStorageAdapter);
|
2024-04-30 06:46:59 +03:00
|
|
|
|
|
|
|
await db.workspacePage.create({
|
|
|
|
data: {
|
|
|
|
workspace: {
|
|
|
|
create: {
|
|
|
|
id: 'public',
|
|
|
|
public: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
pageId: 'private',
|
|
|
|
public: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
await db.workspacePage.create({
|
|
|
|
data: {
|
|
|
|
workspace: {
|
|
|
|
create: {
|
|
|
|
id: 'private',
|
|
|
|
public: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
pageId: 'public',
|
|
|
|
public: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
await db.workspacePage.create({
|
|
|
|
data: {
|
|
|
|
workspace: {
|
|
|
|
create: {
|
|
|
|
id: 'totally-private',
|
|
|
|
public: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
pageId: 'private',
|
|
|
|
public: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2024-08-21 08:30:26 +03:00
|
|
|
test.after.always(async t => {
|
2024-04-30 06:46:59 +03:00
|
|
|
await t.context.app.close();
|
|
|
|
});
|
|
|
|
|
|
|
|
function blob() {
|
|
|
|
function stream() {
|
|
|
|
return Readable.from(Buffer.from('blob'));
|
|
|
|
}
|
|
|
|
|
|
|
|
const init = stream();
|
|
|
|
const ret = {
|
|
|
|
body: init,
|
|
|
|
metadata: {
|
|
|
|
contentType: 'text/plain',
|
|
|
|
lastModified: new Date(),
|
|
|
|
contentLength: 4,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
init.on('end', () => {
|
|
|
|
ret.body = stream();
|
|
|
|
});
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// blob
|
|
|
|
test('should be able to get blob from public workspace', async t => {
|
|
|
|
const { app, u1, storage } = t.context;
|
|
|
|
|
|
|
|
// no authenticated user
|
|
|
|
storage.get.resolves(blob());
|
|
|
|
let res = await request(t.context.app.getHttpServer()).get(
|
|
|
|
'/api/workspaces/public/blobs/test'
|
|
|
|
);
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.OK);
|
|
|
|
t.is(res.get('content-type'), 'text/plain');
|
|
|
|
t.is(res.text, 'blob');
|
|
|
|
|
|
|
|
// authenticated user
|
|
|
|
const cookie = await internalSignIn(app, u1.id);
|
|
|
|
res = await request(t.context.app.getHttpServer())
|
|
|
|
.get('/api/workspaces/public/blobs/test')
|
|
|
|
.set('Cookie', cookie);
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.OK);
|
|
|
|
t.is(res.get('content-type'), 'text/plain');
|
|
|
|
t.is(res.text, 'blob');
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should be able to get private workspace with public pages', async t => {
|
|
|
|
const { app, u1, storage } = t.context;
|
|
|
|
|
|
|
|
// no authenticated user
|
|
|
|
storage.get.resolves(blob());
|
|
|
|
let res = await request(app.getHttpServer()).get(
|
|
|
|
'/api/workspaces/private/blobs/test'
|
|
|
|
);
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.OK);
|
|
|
|
t.is(res.get('content-type'), 'text/plain');
|
|
|
|
t.is(res.text, 'blob');
|
|
|
|
|
|
|
|
// authenticated user
|
|
|
|
const cookie = await internalSignIn(app, u1.id);
|
|
|
|
res = await request(app.getHttpServer())
|
|
|
|
.get('/api/workspaces/private/blobs/test')
|
|
|
|
.set('cookie', cookie);
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.OK);
|
|
|
|
t.is(res.get('content-type'), 'text/plain');
|
|
|
|
t.is(res.text, 'blob');
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should not be able to get private workspace with no public pages', async t => {
|
|
|
|
const { app, u1 } = t.context;
|
|
|
|
|
|
|
|
let res = await request(app.getHttpServer()).get(
|
|
|
|
'/api/workspaces/totally-private/blobs/test'
|
|
|
|
);
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.FORBIDDEN);
|
|
|
|
|
|
|
|
res = await request(app.getHttpServer())
|
|
|
|
.get('/api/workspaces/totally-private/blobs/test')
|
|
|
|
.set('cookie', await internalSignIn(app, u1.id));
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.FORBIDDEN);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should be able to get permission granted workspace', async t => {
|
|
|
|
const { app, u1, db, storage } = t.context;
|
|
|
|
|
|
|
|
const cookie = await internalSignIn(app, u1.id);
|
|
|
|
await db.workspaceUserPermission.create({
|
|
|
|
data: {
|
|
|
|
workspaceId: 'totally-private',
|
|
|
|
userId: u1.id,
|
|
|
|
type: 1,
|
|
|
|
accepted: true,
|
2024-12-09 12:51:54 +03:00
|
|
|
status: WorkspaceMemberStatus.Accepted,
|
2024-04-30 06:46:59 +03:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
storage.get.resolves(blob());
|
|
|
|
const res = await request(app.getHttpServer())
|
|
|
|
.get('/api/workspaces/totally-private/blobs/test')
|
|
|
|
.set('Cookie', cookie);
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.OK);
|
|
|
|
t.is(res.text, 'blob');
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should return 404 if blob not found', async t => {
|
|
|
|
const { app, storage } = t.context;
|
|
|
|
|
|
|
|
// @ts-expect-error mock
|
|
|
|
storage.get.resolves({ body: null });
|
|
|
|
const res = await request(app.getHttpServer()).get(
|
|
|
|
'/api/workspaces/public/blobs/test'
|
|
|
|
);
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.NOT_FOUND);
|
|
|
|
});
|
|
|
|
|
|
|
|
// doc
|
|
|
|
// NOTE: permission checking of doc api is the same with blob api, skip except one
|
|
|
|
test('should not be able to get private workspace with private page', async t => {
|
|
|
|
const { app, u1 } = t.context;
|
|
|
|
|
|
|
|
let res = await request(app.getHttpServer()).get(
|
|
|
|
'/api/workspaces/private/docs/private-page'
|
|
|
|
);
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.FORBIDDEN);
|
|
|
|
|
|
|
|
res = await request(app.getHttpServer())
|
|
|
|
.get('/api/workspaces/private/docs/private-page')
|
|
|
|
.set('cookie', await internalSignIn(app, u1.id));
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.FORBIDDEN);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should be able to get doc', async t => {
|
2024-08-21 08:30:26 +03:00
|
|
|
const { app, workspace: doc } = t.context;
|
2024-04-30 06:46:59 +03:00
|
|
|
|
2024-08-21 08:30:26 +03:00
|
|
|
doc.getDoc.resolves({
|
|
|
|
spaceId: '',
|
|
|
|
docId: '',
|
|
|
|
bin: Buffer.from([0, 0]),
|
2024-04-30 06:46:59 +03:00
|
|
|
timestamp: Date.now(),
|
|
|
|
});
|
|
|
|
|
|
|
|
const res = await request(app.getHttpServer()).get(
|
|
|
|
'/api/workspaces/private/docs/public'
|
|
|
|
);
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.OK);
|
|
|
|
t.is(res.get('content-type'), 'application/octet-stream');
|
|
|
|
t.deepEqual(res.body, Buffer.from([0, 0]));
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should be able to change page publish mode', async t => {
|
2024-08-21 08:30:26 +03:00
|
|
|
const { app, workspace: doc, db } = t.context;
|
2024-04-30 06:46:59 +03:00
|
|
|
|
2024-08-21 08:30:26 +03:00
|
|
|
doc.getDoc.resolves({
|
|
|
|
spaceId: '',
|
|
|
|
docId: '',
|
|
|
|
bin: Buffer.from([0, 0]),
|
2024-04-30 06:46:59 +03:00
|
|
|
timestamp: Date.now(),
|
|
|
|
});
|
|
|
|
|
|
|
|
let res = await request(app.getHttpServer()).get(
|
|
|
|
'/api/workspaces/private/docs/public'
|
|
|
|
);
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.OK);
|
|
|
|
t.is(res.get('publish-mode'), 'page');
|
|
|
|
|
|
|
|
await db.workspacePage.update({
|
|
|
|
where: { workspaceId_pageId: { workspaceId: 'private', pageId: 'public' } },
|
|
|
|
data: { mode: 1 },
|
|
|
|
});
|
|
|
|
|
|
|
|
res = await request(app.getHttpServer()).get(
|
|
|
|
'/api/workspaces/private/docs/public'
|
|
|
|
);
|
|
|
|
|
|
|
|
t.is(res.status, HttpStatus.OK);
|
|
|
|
t.is(res.get('publish-mode'), 'edgeless');
|
|
|
|
});
|