105 lines
3.2 KiB
TypeScript
105 lines
3.2 KiB
TypeScript
import 'reflect-metadata';
|
|
import {
|
|
type CanActivate,
|
|
type ExecutionContext,
|
|
type INestApplication,
|
|
ValidationPipe,
|
|
} from '@nestjs/common';
|
|
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
|
|
import { Test } from '@nestjs/testing';
|
|
import request from 'supertest';
|
|
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { AuthGuard } from '../auth/auth.guard.js';
|
|
import { ProjectBootstrapService } from './project-bootstrap.service.js';
|
|
import { WorkspaceController } from './workspace.controller.js';
|
|
|
|
const bootstrapMock = vi.fn(() =>
|
|
Promise.resolve({
|
|
projectId: 'project-1',
|
|
workspacePath: '/opt/mosaic/.workspaces/users/user-1/project-1',
|
|
}),
|
|
);
|
|
|
|
const authGuard: CanActivate = {
|
|
canActivate(context: ExecutionContext): boolean {
|
|
const requestContext = context.switchToHttp().getRequest<{ user?: { id: string } }>();
|
|
requestContext.user = { id: 'user-1' };
|
|
return true;
|
|
},
|
|
};
|
|
|
|
describe('POST /api/workspaces repoUrl validation', () => {
|
|
let app: INestApplication;
|
|
|
|
beforeAll(async () => {
|
|
const moduleRef = await Test.createTestingModule({
|
|
controllers: [WorkspaceController],
|
|
providers: [
|
|
{
|
|
provide: ProjectBootstrapService,
|
|
useValue: { bootstrap: bootstrapMock },
|
|
},
|
|
],
|
|
})
|
|
.overrideGuard(AuthGuard)
|
|
.useValue(authGuard)
|
|
.compile();
|
|
|
|
app = moduleRef.createNestApplication<NestFastifyApplication>(new FastifyAdapter());
|
|
app.useGlobalPipes(
|
|
new ValidationPipe({
|
|
whitelist: true,
|
|
forbidNonWhitelisted: true,
|
|
transform: true,
|
|
}),
|
|
);
|
|
await app.init();
|
|
await app.getHttpAdapter().getInstance().ready();
|
|
});
|
|
|
|
beforeEach(() => {
|
|
bootstrapMock.mockClear();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
});
|
|
|
|
it.each([
|
|
['a leading-dash value', '--upload-pack=sh -c id'],
|
|
['an ext remote helper', 'ext::sh -c id'],
|
|
['a file URL', 'file:///tmp/repository'],
|
|
['an unparseable value', 'not a url'],
|
|
['an SSH shorthand', '[email protected]:acme/repository.git'],
|
|
['a scheme without //', 'https:example.com/acme/repository.git'],
|
|
['a hostless git URL', 'git:///tmp/repository'],
|
|
])('returns 400 for %s', async (_description, repoUrl) => {
|
|
const response = await request(app.getHttpServer())
|
|
.post('/api/workspaces')
|
|
.send({ name: 'Example', repoUrl })
|
|
.set('Content-Type', 'application/json');
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(bootstrapMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it.each([
|
|
['a plain HTTPS repository URL', 'https://example.com/acme/repository.git'],
|
|
['a git protocol repository URL', 'git://example.com/acme/repository.git'],
|
|
])('accepts %s', async (_description, repoUrl) => {
|
|
const response = await request(app.getHttpServer())
|
|
.post('/api/workspaces')
|
|
.send({ name: 'Example', repoUrl })
|
|
.set('Content-Type', 'application/json');
|
|
|
|
expect(response.status).toBe(201);
|
|
expect(bootstrapMock).toHaveBeenCalledWith({
|
|
name: 'Example',
|
|
description: undefined,
|
|
userId: 'user-1',
|
|
teamId: undefined,
|
|
repoUrl,
|
|
});
|
|
});
|
|
});
|