Skip to content

Commit

Permalink
Merge branch 'dev' into feature/voyage-submission
Browse files Browse the repository at this point in the history
  • Loading branch information
cherylli committed Apr 15, 2024
2 parents 98bddf6 + 9ad8509 commit 3aa6109
Show file tree
Hide file tree
Showing 2 changed files with 377 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Another example [here](https://co-pilot.dev/changelog)
- Add e2e tests for techs controller ([#103](https://github.com/chingu-x/chingu-dashboard-be/pull/103))
- Add check-in form database implementation and seed data ([#105](https://github.com/chingu-x/chingu-dashboard-be/pull/105))
- Add e2e tests for forms controller ([#107](https://github.com/chingu-x/chingu-dashboard-be/pull/107))
- Add e2e tests for resources controller ([#109](https://github.com/chingu-x/chingu-dashboard-be/pull/109))
- Add e2e tests for sprint controller ([#113](https://github.com/chingu-x/chingu-dashboard-be/pull/113))
- Add new endpoint to revoke refresh token ([#116](https://github.com/chingu-x/chingu-dashboard-be/pull/116))
- Add meetingId to sprints/teams endpoint (([#119](https://github.com/chingu-x/chingu-dashboard-be/pull/119)))
Expand Down
376 changes: 376 additions & 0 deletions test/resources.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,376 @@
import { Test, TestingModule } from "@nestjs/testing";
import { INestApplication, ValidationPipe } from "@nestjs/common";
import { seed } from "../prisma/seed/seed";
import * as request from "supertest";
import { AppModule } from "../src/app.module";
import { PrismaService } from "../src/prisma/prisma.service";
import { CreateResourceDto } from "src/resources/dto/create-resource.dto";
import { UpdateResourceDto } from "src/resources/dto/update-resource.dto";
import { extractResCookieValueByKey } from "./utils";

const loginUser = async (
email: string,
password: string,
app: INestApplication,
) => {
const res = await request(app.getHttpServer())
.post("/auth/login")
.send({
email,
password,
})
.expect(200);

return extractResCookieValueByKey(
res.headers["set-cookie"],
"access_token",
);
};

const findVoyageTeamId = async (email: string, prisma: PrismaService) => {
return prisma.voyageTeamMember.findFirst({
where: {
member: {
email,
},
},
select: {
userId: true,
voyageTeamId: true,
},
});
};

const findOwnResource = async (email: string, prisma: PrismaService) => {
return prisma.teamResource.findFirst({
where: {
addedBy: {
member: {
email,
},
},
},
});
};

const countResources = async (voyageTeamId: number, prisma: PrismaService) => {
return prisma.teamResource.count({
where: {
addedBy: {
voyageTeamId,
},
},
});
};

describe("ResourcesController (e2e)", () => {
let app: INestApplication;
let prisma: PrismaService;
// main user
const userEmail: string = "dan@random.com";
let voyageTeamId: number;
let userAccessToken: string;
// user for testing access control
const otherUserEmail: string = "JosoMadar@dayrep.com";
let otherVoyageTeamId: number;
let otherUserAccessToken: string;

const memberShape = {
avatar: expect.any(String),
firstName: expect.any(String),
lastName: expect.any(String),
id: expect.any(String),
};

const resourceShape = {
id: expect.any(Number),
teamMemberId: expect.any(Number),
url: expect.any(String),
title: expect.any(String),
createdAt: expect.any(String),
updatedAt: expect.any(String),
};

beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();

await seed();

app = moduleFixture.createNestApplication();
prisma = moduleFixture.get<PrismaService>(PrismaService);
app.useGlobalPipes(new ValidationPipe());
await app.init();

// voyageTeamId of main user
({ voyageTeamId } = await findVoyageTeamId(userEmail, prisma));
userAccessToken = await loginUser(userEmail, "password", app);

({ voyageTeamId: otherVoyageTeamId } = await findVoyageTeamId(
otherUserEmail,
prisma,
));
otherUserAccessToken = await loginUser(otherUserEmail, "password", app);

if (voyageTeamId === otherVoyageTeamId) {
throw new Error("Voyage team IDs should be different");
}
});

afterAll(async () => {
await prisma.$disconnect();
await app.close();
});

describe("/POST voyages/:teamId/resources", () => {
it("should return 201 and create a new resource", async () => {
const newResource: CreateResourceDto = {
url: "http://www.github.com/chingux",
title: "Chingu Github repo",
};
const initialResourceCount: number = await countResources(
voyageTeamId,
prisma,
);

await request(app.getHttpServer())
.post(`/voyages/teams/${voyageTeamId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.send(newResource)
.expect(201)
.expect("Content-Type", /json/)
.expect(async (res) => {
expect(res.status).toBe(201);
expect(res.body).toEqual({ ...resourceShape });
const createdResource =
await prisma.teamResource.findUnique({
where: { id: res.body.id },
});
expect(createdResource).not.toBeNull();
});
const updatedResourceCount = await countResources(
voyageTeamId,
prisma,
);
expect(updatedResourceCount).toBe(initialResourceCount + 1);
});

it("should return 400 for invalid request body", async () => {
const invalidResource = {
title: "Chingu Github repo",
};

await request(app.getHttpServer())
.post(`/voyages/teams/${voyageTeamId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.send(invalidResource)
.expect(400);
});

it("should return 404 for invalid teamId", async () => {
const invalidTeamId = 999;
const newResource: CreateResourceDto = {
url: "http://www.github.com/chingux2",
title: "Chingu Github repo",
};

await request(app.getHttpServer())
.post(`/voyages/teams/${invalidTeamId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.send(newResource)
.expect(404);
});

it("should return 401 and not allow users to POST to other teams' resources", async () => {
const newResource: CreateResourceDto = {
url: "http://www.github.com/chingux3",
title: "Chingu Github repo",
};

await request(app.getHttpServer())
.post(`/voyages/teams/${voyageTeamId}`)
.set("Authorization", `Bearer ${otherUserAccessToken}`)
.send(newResource)
.expect(401);
});
});

describe("/GET voyages/:teamId/resources", () => {
it("should return 200 and retrieve all resources for the team.", async () => {
const resourceCount: number = await prisma.teamResource.count({
where: {
addedBy: {
voyageTeamId,
},
},
});

await request(app.getHttpServer())
.get(`/voyages/teams/${voyageTeamId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.expect(200)
.expect("Content-Type", /json/)
.expect((res) => {
expect(res.body).toEqual(
expect.arrayContaining([
{
...resourceShape,
addedBy: expect.objectContaining({
member: memberShape,
}),
},
]),
);
expect(res.body).toHaveLength(resourceCount);
});
});

it("should return 404 for invalid teamId", async () => {
const invalidTeamId = 999;

await request(app.getHttpServer())
.get(`/voyages/teams/${invalidTeamId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.expect(404);
});

it("should return 401 and not allow users to GET other teams' resources", async () => {
await request(app.getHttpServer())
.get(`/voyages/teams/${voyageTeamId}`)
.set("Authorization", `Bearer ${otherUserAccessToken}`)
.expect(401);
});
});

describe("/PATCH :teamId/resources/:resourceId", () => {
it("should return 200 and update a resource", async () => {
const resourceToPatch = await findOwnResource(userEmail, prisma);
const resourceId: number = resourceToPatch.id;
const patchedResource: UpdateResourceDto = {
url: "http://www.github.com/chingu-x/chingu-dashboard-be",
title: "Chingu Github BE repo",
};

await request(app.getHttpServer())
.patch(`/voyages/resources/${resourceId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.send(patchedResource)
.expect(200)
.expect("Content-Type", /json/)
.expect(async (res) => {
expect(res.body).toEqual({
...resourceShape,
});
const updatedResource =
await prisma.teamResource.findUnique({
where: { id: resourceId },
});
expect(updatedResource.url).toBe(patchedResource.url);
expect(updatedResource.title).toBe(patchedResource.title);
});
});

it("should return 404 for invalid resourceId", async () => {
const invalidResourceId = 999;
const patchedResource: UpdateResourceDto = {
url: "http://www.github.com/chingu-x/chingu-dashboard-be",
title: "Chingu Github BE repo",
};

await request(app.getHttpServer())
.patch(`/voyages/resources/${invalidResourceId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.send(patchedResource)
.expect(404)
.expect("Content-Type", /json/);
});

it("should return 400 for invalid request body", async () => {
const resourceToPatch = await findOwnResource(userEmail, prisma);
const resourceId: number = resourceToPatch.id;
const invalidResource = {
url: "Chingu Github repo",
};

await request(app.getHttpServer())
.patch(`/voyages/resources/${resourceId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.send(invalidResource)
.expect(400);
});

it("should return 401 if a user tries to PATCH a resource created by someone else", async () => {
const resourceToPatch = await findOwnResource(userEmail, prisma);
const resourceId: number = resourceToPatch.id;
const patchedResource: UpdateResourceDto = {
url: "http://www.github.com/chingu-x/chingu-dashboard-be",
title: "Chingu Github BE repo",
};

await request(app.getHttpServer())
.patch(`/voyages/resources/${resourceId}`)
.set("Authorization", `Bearer ${otherUserAccessToken}`)
.send(patchedResource)
.expect(401);
});
});

describe("/DELETE :teamId/resources/:resourceId", () => {
it("should return 200 after deleting a resource", async () => {
const resourceToDelete = await findOwnResource(userEmail, prisma);
const resourceId: number = resourceToDelete.id;
const initialResourceCount = await countResources(
voyageTeamId,
prisma,
);

await request(app.getHttpServer())
.delete(`/voyages/resources/${resourceId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.expect(200)
.expect(async (res) => {
expect(res.body).toEqual({
...resourceShape,
});
const updatedResourceCount = await countResources(
voyageTeamId,
prisma,
);
expect(updatedResourceCount).toBe(initialResourceCount - 1);

const deletedResource =
await prisma.teamResource.findUnique({
where: { id: resourceId },
});
expect(deletedResource).toBeNull();
});
});

it("should return 404 for invalid resourceId", async () => {
const invalidResourceId = 999;

await request(app.getHttpServer())
.delete(`/voyages/resources/${invalidResourceId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.expect(404);
});

it("should return 400 for invalid request body", async () => {
await request(app.getHttpServer())
.delete(`/voyages/resources/rm -rf`)
.set("Authorization", `Bearer ${userAccessToken}`)
.expect(400);
});

it("should return 401 if a user tries to DELETE a resource created by someone else", async () => {
const resourceToDelete = await findOwnResource(userEmail, prisma);
const resourceId: number = resourceToDelete.id;

await request(app.getHttpServer())
.delete(`/voyages/resources/${resourceId}`)
.set("Authorization", `Bearer ${otherUserAccessToken}`)
.expect(401);
});
});
});

0 comments on commit 3aa6109

Please sign in to comment.