361 lines
11 KiB
TypeScript
361 lines
11 KiB
TypeScript
import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
|
|
import type { FastifyInstance } from "fastify";
|
|
import { createTestApp, cleanDb, signupUser } from "./helpers.js";
|
|
import { db } from "../src/db/index.js";
|
|
import { packages } from "../src/db/schema.js";
|
|
|
|
let app: FastifyInstance;
|
|
|
|
beforeAll(async () => {
|
|
app = await createTestApp();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await cleanDb();
|
|
});
|
|
|
|
async function setupUserWithProject() {
|
|
const signup = await signupUser(app, "user@example.com", "Org");
|
|
const { accessToken, accounts } = signup.json();
|
|
const accountId = accounts[0].id as string;
|
|
|
|
const projectRes = await app.inject({
|
|
method: "POST",
|
|
url: "/projects",
|
|
headers: { authorization: `Bearer ${accessToken}`, "x-account-id": accountId },
|
|
payload: { name: "My Project" },
|
|
});
|
|
const projectId = projectRes.json().id as string;
|
|
|
|
return { accessToken, accountId, projectId };
|
|
}
|
|
|
|
function headers(accessToken: string, accountId: string) {
|
|
return { authorization: `Bearer ${accessToken}`, "x-account-id": accountId };
|
|
}
|
|
|
|
async function seedSmallPackage() {
|
|
const [pkg] = await db
|
|
.insert(packages)
|
|
.values({ slug: "small", name: "Small", vcpu: 1, ram: 1024, disk: 20, priceMonthly: "5.00" })
|
|
.returning();
|
|
return pkg;
|
|
}
|
|
|
|
describe("GET /projects/:projectId/wms", () => {
|
|
it("returns empty list when no WMs exist", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
const res = await app.inject({
|
|
method: "GET",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
});
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
expect(res.json()).toEqual([]);
|
|
});
|
|
|
|
it("returns WMs for the project", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "WM 1", vcpu: 1, ram: 512, disk: 10 },
|
|
});
|
|
await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "WM 2", vcpu: 2, ram: 1024, disk: 20 },
|
|
});
|
|
|
|
const res = await app.inject({
|
|
method: "GET",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
});
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
expect(res.json()).toHaveLength(2);
|
|
});
|
|
|
|
it("returns 404 for a project in another account", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
// Create second account
|
|
const accountRes = await app.inject({
|
|
method: "POST",
|
|
url: "/accounts",
|
|
headers: { authorization: `Bearer ${accessToken}` },
|
|
payload: { name: "Org 2" },
|
|
});
|
|
const secondAccountId = accountRes.json().id;
|
|
|
|
const res = await app.inject({
|
|
method: "GET",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, secondAccountId),
|
|
});
|
|
|
|
expect(res.statusCode).toBe(404);
|
|
});
|
|
});
|
|
|
|
describe("POST /projects/:projectId/wms", () => {
|
|
it("creates a WM from a package", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
const pkg = await seedSmallPackage();
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "My WM", packageId: pkg.id },
|
|
});
|
|
|
|
expect(res.statusCode).toBe(201);
|
|
const body = res.json();
|
|
expect(body.name).toBe("My WM");
|
|
expect(body.packageId).toBe(pkg.id);
|
|
expect(body.vcpu).toBe(1);
|
|
expect(body.ram).toBe(1024);
|
|
expect(body.disk).toBe(20);
|
|
});
|
|
|
|
it("creates a WM with custom settings", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "Custom WM", vcpu: 4, ram: 8192, disk: 100 },
|
|
});
|
|
|
|
expect(res.statusCode).toBe(201);
|
|
const body = res.json();
|
|
expect(body.packageId).toBeNull();
|
|
expect(body.vcpu).toBe(4);
|
|
expect(body.ram).toBe(8192);
|
|
expect(body.disk).toBe(100);
|
|
});
|
|
|
|
it("returns 400 if neither packageId nor full custom settings provided", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "Bad WM", vcpu: 2 },
|
|
});
|
|
|
|
expect(res.statusCode).toBe(400);
|
|
expect(res.json().error).toMatch(/packageId/);
|
|
});
|
|
|
|
it("returns 404 for non-existent package", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "WM", packageId: "00000000-0000-0000-0000-000000000000" },
|
|
});
|
|
|
|
expect(res.statusCode).toBe(404);
|
|
expect(res.json().error).toMatch(/Package/);
|
|
});
|
|
|
|
it("returns 404 for a project in another account", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
const accountRes = await app.inject({
|
|
method: "POST",
|
|
url: "/accounts",
|
|
headers: { authorization: `Bearer ${accessToken}` },
|
|
payload: { name: "Org 2" },
|
|
});
|
|
const secondAccountId = accountRes.json().id;
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, secondAccountId),
|
|
payload: { name: "WM", vcpu: 1, ram: 512, disk: 10 },
|
|
});
|
|
|
|
expect(res.statusCode).toBe(404);
|
|
});
|
|
});
|
|
|
|
describe("PATCH /projects/:projectId/wms/:id", () => {
|
|
it("updates WM name", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
const create = await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "Old Name", vcpu: 1, ram: 512, disk: 10 },
|
|
});
|
|
const wmId = create.json().id;
|
|
|
|
const res = await app.inject({
|
|
method: "PATCH",
|
|
url: `/projects/${projectId}/wms/${wmId}`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "New Name" },
|
|
});
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
expect(res.json().name).toBe("New Name");
|
|
expect(res.json().vcpu).toBe(1);
|
|
});
|
|
|
|
it("updates individual settings (e.g. only disk)", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
const pkg = await seedSmallPackage();
|
|
|
|
const create = await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "WM", packageId: pkg.id },
|
|
});
|
|
const wmId = create.json().id;
|
|
|
|
const res = await app.inject({
|
|
method: "PATCH",
|
|
url: `/projects/${projectId}/wms/${wmId}`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { disk: 40 },
|
|
});
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
expect(res.json().disk).toBe(40);
|
|
expect(res.json().vcpu).toBe(1);
|
|
expect(res.json().ram).toBe(1024);
|
|
});
|
|
|
|
it("returns 404 for non-existent WM", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
const res = await app.inject({
|
|
method: "PATCH",
|
|
url: `/projects/${projectId}/wms/00000000-0000-0000-0000-000000000000`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "Nope" },
|
|
});
|
|
|
|
expect(res.statusCode).toBe(404);
|
|
});
|
|
|
|
it("returns 404 for project in another account", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
const create = await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "WM", vcpu: 1, ram: 512, disk: 10 },
|
|
});
|
|
const wmId = create.json().id;
|
|
|
|
const accountRes = await app.inject({
|
|
method: "POST",
|
|
url: "/accounts",
|
|
headers: { authorization: `Bearer ${accessToken}` },
|
|
payload: { name: "Org 2" },
|
|
});
|
|
const secondAccountId = accountRes.json().id;
|
|
|
|
const res = await app.inject({
|
|
method: "PATCH",
|
|
url: `/projects/${projectId}/wms/${wmId}`,
|
|
headers: headers(accessToken, secondAccountId),
|
|
payload: { name: "Hacked" },
|
|
});
|
|
|
|
expect(res.statusCode).toBe(404);
|
|
});
|
|
});
|
|
|
|
describe("DELETE /projects/:projectId/wms/:id", () => {
|
|
it("deletes a WM", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
const create = await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "To Delete", vcpu: 1, ram: 512, disk: 10 },
|
|
});
|
|
const wmId = create.json().id;
|
|
|
|
const res = await app.inject({
|
|
method: "DELETE",
|
|
url: `/projects/${projectId}/wms/${wmId}`,
|
|
headers: headers(accessToken, accountId),
|
|
});
|
|
|
|
expect(res.statusCode).toBe(204);
|
|
|
|
// Verify it's gone
|
|
const list = await app.inject({
|
|
method: "GET",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
});
|
|
expect(list.json()).toEqual([]);
|
|
});
|
|
|
|
it("returns 404 for non-existent WM", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
const res = await app.inject({
|
|
method: "DELETE",
|
|
url: `/projects/${projectId}/wms/00000000-0000-0000-0000-000000000000`,
|
|
headers: headers(accessToken, accountId),
|
|
});
|
|
|
|
expect(res.statusCode).toBe(404);
|
|
});
|
|
|
|
it("returns 404 for project in another account", async () => {
|
|
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
|
|
|
const create = await app.inject({
|
|
method: "POST",
|
|
url: `/projects/${projectId}/wms`,
|
|
headers: headers(accessToken, accountId),
|
|
payload: { name: "WM", vcpu: 1, ram: 512, disk: 10 },
|
|
});
|
|
const wmId = create.json().id;
|
|
|
|
const accountRes = await app.inject({
|
|
method: "POST",
|
|
url: "/accounts",
|
|
headers: { authorization: `Bearer ${accessToken}` },
|
|
payload: { name: "Org 2" },
|
|
});
|
|
const secondAccountId = accountRes.json().id;
|
|
|
|
const res = await app.inject({
|
|
method: "DELETE",
|
|
url: `/projects/${projectId}/wms/${wmId}`,
|
|
headers: headers(accessToken, secondAccountId),
|
|
});
|
|
|
|
expect(res.statusCode).toBe(404);
|
|
});
|
|
});
|