diff --git a/src/reference/openapi.yml b/src/reference/openapi.yml index c9f6346d3..5c097bc70 100644 --- a/src/reference/openapi.yml +++ b/src/reference/openapi.yml @@ -1176,8 +1176,6 @@ paths: type: string metal: type: string - x-stoplight: - id: 9afe0zlf0hr2b devices: type: array items: @@ -4239,8 +4237,6 @@ paths: type: string type: type: string - x-stoplight: - id: 6yohqfohiv47u required: - id - name @@ -9688,8 +9684,6 @@ paths: properties: id: type: integer - x-stoplight: - id: 965rh7aagxj8x examples: Example 1: value: @@ -9735,118 +9729,102 @@ paths: properties: id: type: integer - x-stoplight: - id: qqon32evab2o5 title: type: object - x-stoplight: - id: qfn9vuux1vjp1 required: - customer - tester properties: customer: type: string - x-stoplight: - id: tyx8mtant8jej tester: type: string - x-stoplight: - id: cupl63jal29yu startDate: type: string - x-stoplight: - id: lif1wyyh6miiy format: date-time endDate: type: string - x-stoplight: - id: dhx6h9l494pyw format: date-time customer: type: object - x-stoplight: - id: b6vdml702n2vr required: - id - name properties: id: type: integer - x-stoplight: - id: hzxvga289ilzv name: type: string - x-stoplight: - id: zxk4wi6clmaxr project: type: object - x-stoplight: - id: 9dnwnxk98slrj required: - id - name properties: id: type: integer - x-stoplight: - id: 795lyzmcn2f3i name: type: string - x-stoplight: - id: lonlyyu0xqpx9 testType: type: object - x-stoplight: - id: y2l96ujpgntcx required: - id - name properties: id: type: integer - x-stoplight: - id: 5ut1dht9q3e5f name: type: string - x-stoplight: - id: 3zafe5jvg89hd deviceList: type: array - x-stoplight: - id: 8ac5l94cifolj items: - x-stoplight: - id: p874do1llblvt type: object properties: id: type: integer - x-stoplight: - id: 64zrvfx7v23m8 name: type: string - x-stoplight: - id: 838zaws7zfcb6 required: - id - name csm: type: object - x-stoplight: - id: ye4vllt9hwd40 required: - id - name properties: id: type: integer - x-stoplight: - id: 29ini0mkuenx4 name: type: string - x-stoplight: - id: 0v4z9eu7jfeja + roles: + type: array + items: + type: object + properties: + role: + type: object + properties: + id: + type: integer + name: + type: string + required: + - id + - name + user: + type: object + properties: + id: + type: integer + name: + type: string + surname: + type: string + required: + - id + - name + - surname required: - id - title @@ -9857,6 +9835,38 @@ paths: - testType - deviceList - csm + examples: + Example 1: + value: + id: 1 + title: + customer: Customer Title + tester: Tester Title + startDate: '2019-08-24T14:15:22Z' + endDate: '2019-08-24T14:15:22Z' + customer: + id: 1 + name: My Customer + project: + id: 1 + name: My Project + testType: + id: 1 + name: Bughunting + deviceList: + - id: 1 + name: Android + csm: + id: 1 + name: Name Surname + roles: + - role: + id: 1 + name: PM + user: + id: 1 + name: Name + surname: Surname security: - JWT: [] '/customers/{customer}/projects': @@ -9879,21 +9889,13 @@ paths: properties: results: type: array - x-stoplight: - id: 9ccc89bhovco5 items: - x-stoplight: - id: zog5qqnv5h7rb type: object properties: id: type: integer - x-stoplight: - id: xapq51u019ada name: type: string - x-stoplight: - id: 8fxvw6c3m46bv required: - id - name @@ -11057,8 +11059,18 @@ components: type: integer csm: type: number - x-stoplight: - id: tczilke0whidg + roles: + type: array + items: + type: object + properties: + role: + type: number + user: + type: number + required: + - role + - user required: - project - testType diff --git a/src/routes/dossiers/_post/creation.spec.ts b/src/routes/dossiers/_post/creation.spec.ts index 218dfdaeb..6aa350cfc 100644 --- a/src/routes/dossiers/_post/creation.spec.ts +++ b/src/routes/dossiers/_post/creation.spec.ts @@ -15,6 +15,14 @@ const baseRequest = { describe("Route POST /dossiers", () => { beforeAll(async () => { + await tryber.tables.WpAppqEvdProfile.do().insert({ + id: 1, + wp_user_id: 100, + name: "", + email: "", + education_id: 1, + employment_id: 1, + }); await tryber.tables.WpAppqCustomer.do().insert({ id: 1, company: "Test Company", @@ -48,6 +56,10 @@ describe("Route POST /dossiers", () => { architecture: 1, }, ]); + + await tryber.tables.CustomRoles.do().insert([ + { id: 1, name: "Test Role", olp: '["appq_bugs"]' }, + ]); }); afterAll(async () => { @@ -55,9 +67,12 @@ describe("Route POST /dossiers", () => { await tryber.tables.WpAppqProject.do().delete(); await tryber.tables.WpAppqCampaignType.do().delete(); await tryber.tables.WpAppqEvdPlatform.do().delete(); + await tryber.tables.WpAppqEvdProfile.do().delete(); + await tryber.tables.CustomRoles.do().delete(); }); afterEach(async () => { await tryber.tables.WpAppqEvdCampaign.do().delete(); + await tryber.tables.CampaignCustomRoles.do().delete(); }); it("Should create a campaign", async () => { @@ -270,4 +285,55 @@ describe("Route POST /dossiers", () => { expect(campaign).toHaveProperty("os", "1,2"); expect(campaign).toHaveProperty("form_factor", "0,1"); }); + + it("Should return 406 if adding a role that does not exist", async () => { + const response = await request(app) + .post("/dossiers") + .set("authorization", "Bearer admin") + .send({ ...baseRequest, roles: [{ role: 100, user: 1 }] }); + + expect(response.status).toBe(406); + }); + + it("Should return 406 if adding a role to a user that does not exist", async () => { + const response = await request(app) + .post("/dossiers") + .set("authorization", "Bearer admin") + .send({ ...baseRequest, roles: [{ role: 1, user: 100 }] }); + + expect(response.status).toBe(406); + }); + + it("Should link the roles to the campaign", async () => { + const response = await request(app) + .post("/dossiers") + .set("authorization", "Bearer admin") + .send({ ...baseRequest, roles: [{ role: 1, user: 1 }] }); + + const id = response.body.id; + + const roles = await tryber.tables.CampaignCustomRoles.do() + .select() + .where({ campaign_id: id }); + expect(roles).toHaveLength(1); + expect(roles[0]).toHaveProperty("custom_role_id", 1); + expect(roles[0]).toHaveProperty("tester_id", 1); + }); + + it("Should set the olp roles to the campaign", async () => { + const response = await request(app) + .post("/dossiers") + .set("authorization", "Bearer admin") + .send({ ...baseRequest, roles: [{ role: 1, user: 1 }] }); + + const id = response.body.id; + + const olps = await tryber.tables.WpAppqOlpPermissions.do() + .select() + .where({ main_id: id }); + expect(olps).toHaveLength(1); + expect(olps[0]).toHaveProperty("type", "appq_bugs"); + expect(olps[0]).toHaveProperty("main_type", "campaign"); + expect(olps[0]).toHaveProperty("wp_user_id", 100); + }); }); diff --git a/src/routes/dossiers/_post/index.ts b/src/routes/dossiers/_post/index.ts index f9155f13b..69cf3cb42 100644 --- a/src/routes/dossiers/_post/index.ts +++ b/src/routes/dossiers/_post/index.ts @@ -10,6 +10,10 @@ export default class RouteItem extends AdminRoute<{ }> { protected async filter() { if (!(await super.filter())) return false; + if (await this.invalidRolesSubmitted()) { + this.setError(406, new OpenapiError("Invalid roles submitted")); + return false; + } if (!(await this.projectExists())) { this.setError(400, new OpenapiError("Project does not exist")); return false; @@ -26,6 +30,23 @@ export default class RouteItem extends AdminRoute<{ return true; } + private async invalidRolesSubmitted() { + const { roles } = this.getBody(); + if (!roles) return false; + const roleIds = [...new Set(roles.map((role) => role.role))]; + const rolesExist = await tryber.tables.CustomRoles.do() + .select() + .whereIn("id", roleIds); + if (rolesExist.length !== roleIds.length) return true; + + const userIds = [...new Set(roles.map((role) => role.user))]; + const usersExist = await tryber.tables.WpAppqEvdProfile.do() + .select() + .whereIn("id", userIds); + if (usersExist.length !== userIds.length) return true; + return false; + } + private async projectExists(): Promise { const { project: projectId } = this.getBody(); const project = await tryber.tables.WpAppqProject.do() @@ -58,8 +79,11 @@ export default class RouteItem extends AdminRoute<{ protected async prepare(): Promise { try { + const campaignId = await this.createCampaign(); + await this.linkRolesToCampaign(campaignId); + this.setSuccess(201, { - id: await this.createCampaign(), + id: campaignId, }); } catch (e) { this.setError(500, e as OpenapiError); @@ -90,6 +114,54 @@ export default class RouteItem extends AdminRoute<{ return results[0].id ?? results[0]; } + private async linkRolesToCampaign(campaignId: number) { + const roles = this.getBody().roles; + if (!roles) return; + + await tryber.tables.CampaignCustomRoles.do().insert( + roles.map((role) => ({ + campaign_id: campaignId, + custom_role_id: role.role, + tester_id: role.user, + })) + ); + + await this.assignOlps(campaignId); + } + + private async assignOlps(campaignId: number) { + const roles = this.getBody().roles; + if (!roles) return; + + const roleOlps = await tryber.tables.CustomRoles.do() + .select("id", "olp") + .whereIn( + "id", + roles.map((role) => role.role) + ); + const wpUserIds = await tryber.tables.WpAppqEvdProfile.do() + .select("id", "wp_user_id") + .whereIn( + "id", + roles.map((role) => role.user) + ); + for (const role of roles) { + const olp = roleOlps.find((r) => r.id === role.role)?.olp; + const wpUserId = wpUserIds.find((r) => r.id === role.user); + if (olp && wpUserId) { + const olpObject = JSON.parse(olp); + await tryber.tables.WpAppqOlpPermissions.do().insert( + olpObject.map((olpType: string) => ({ + main_id: campaignId, + main_type: "campaign", + type: olpType, + wp_user_id: wpUserId.wp_user_id, + })) + ); + } + } + } + private getCsmId() { const { csm } = this.getBody(); return csm ? csm : this.getTesterId(); diff --git a/src/routes/dossiers/campaignId/_get/index.spec.ts b/src/routes/dossiers/campaignId/_get/index.spec.ts index f81b6c1e1..dfd6971ae 100644 --- a/src/routes/dossiers/campaignId/_get/index.spec.ts +++ b/src/routes/dossiers/campaignId/_get/index.spec.ts @@ -16,15 +16,27 @@ describe("Route GET /dossiers/:id", () => { edited_by: 1, }); - await tryber.tables.WpAppqEvdProfile.do().insert({ - id: 1, - wp_user_id: 1, + const profile = { name: "Test", - surname: "CSM", + surname: "Profile", email: "", education_id: 1, employment_id: 1, - }); + }; + await tryber.tables.WpAppqEvdProfile.do().insert([ + { + ...profile, + id: 1, + wp_user_id: 1, + surname: "CSM", + }, + { + ...profile, + id: 2, + wp_user_id: 2, + surname: "PM", + }, + ]); await tryber.tables.WpAppqCampaignType.do().insert({ id: 1, @@ -57,6 +69,20 @@ describe("Route GET /dossiers/:id", () => { pm_id: 1, customer_id: 0, }); + + await tryber.tables.CustomRoles.do().insert([ + { + id: 1, + name: "Test Role", + olp: '["appq_bug"]', + }, + ]); + + await tryber.tables.CampaignCustomRoles.do().insert({ + campaign_id: 1, + custom_role_id: 1, + tester_id: 2, + }); }); afterAll(async () => { @@ -82,7 +108,6 @@ describe("Route GET /dossiers/:id", () => { const response = await request(app) .get("/dossiers/1") .set("authorization", "Bearer tester"); - console.log(response.body); expect(response.status).toBe(403); }); @@ -196,4 +221,21 @@ describe("Route GET /dossiers/:id", () => { expect(response.body.customer).toHaveProperty("id", 1); expect(response.body.customer).toHaveProperty("name", "Test Company"); }); + + it("Should return the roles", async () => { + const response = await request(app) + .get("/dossiers/1") + .set("authorization", "Bearer admin"); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty("roles"); + expect(response.body.roles).toHaveLength(1); + expect(response.body.roles[0]).toHaveProperty("role"); + expect(response.body.roles[0].role).toHaveProperty("id", 1); + expect(response.body.roles[0].role).toHaveProperty("name", "Test Role"); + expect(response.body.roles[0]).toHaveProperty("user"); + expect(response.body.roles[0].user).toHaveProperty("id", 2); + expect(response.body.roles[0].user).toHaveProperty("name", "Test"); + expect(response.body.roles[0].user).toHaveProperty("surname", "PM"); + }); }); diff --git a/src/routes/dossiers/campaignId/_get/index.ts b/src/routes/dossiers/campaignId/_get/index.ts index 19034d493..440921c18 100644 --- a/src/routes/dossiers/campaignId/_get/index.ts +++ b/src/routes/dossiers/campaignId/_get/index.ts @@ -29,12 +29,12 @@ export default class RouteItem extends AdminRoute<{ private async getCampaign() { const campaign = await tryber.tables.WpAppqEvdCampaign.do() .select( - "end_date", + tryber.fn.charDate("start_date"), + tryber.fn.charDate("end_date"), tryber.ref("id").withSchema("wp_appq_evd_campaign"), "title", "customer_title", "project_id", - "start_date", "campaign_type_id", "os", tryber @@ -89,7 +89,30 @@ export default class RouteItem extends AdminRoute<{ .select("id", "name") .whereIn("id", campaign.os.split(",")); - return { ...campaign, devices }; + const roles = await tryber.tables.CustomRoles.do() + .join( + "campaign_custom_roles", + "campaign_custom_roles.custom_role_id", + "custom_roles.id" + ) + .join( + "wp_appq_evd_profile", + "wp_appq_evd_profile.id", + "campaign_custom_roles.tester_id" + ) + .select( + tryber.ref("id").withSchema("wp_appq_evd_profile").as("tester_id"), + tryber.ref("name").withSchema("wp_appq_evd_profile").as("tester_name"), + tryber + .ref("surname") + .withSchema("wp_appq_evd_profile") + .as("tester_surname"), + tryber.ref("id").withSchema("custom_roles").as("role_id"), + tryber.ref("name").withSchema("custom_roles").as("role_name") + ) + .where("campaign_custom_roles.campaign_id", this.campaignId); + + return { ...campaign, devices, roles }; } get campaign() { @@ -118,6 +141,7 @@ export default class RouteItem extends AdminRoute<{ } protected async prepare(): Promise { + console.log(this.campaign.start_date); try { this.setSuccess(200, { id: this.campaign.id, @@ -137,16 +161,39 @@ export default class RouteItem extends AdminRoute<{ id: this.campaign.campaign_type_id, name: this.campaign.campaign_type_name, }, - startDate: this.campaign.start_date, - endDate: this.campaign.end_date, + startDate: this.formatDate(this.campaign.start_date), + endDate: this.formatDate(this.campaign.end_date), deviceList: this.campaign.devices, csm: { id: this.campaign.pm_id, name: `${this.campaign.pm_name} ${this.campaign.pm_surname}`, }, + ...(this.campaign.roles.length + ? { + roles: this.campaign.roles.map((item) => { + return { + role: { + id: item.role_id, + name: item.role_name, + }, + user: { + id: item.tester_id, + name: item.tester_name, + surname: item.tester_surname, + }, + }; + }), + } + : {}), }); } catch (e) { this.setError(500, e as OpenapiError); } } + + private formatDate(dateTime: string) { + const [date, time] = dateTime.split(" "); + if (!date || !time) return dateTime; + return `${date}T${time}Z`; + } } diff --git a/src/routes/dossiers/campaignId/_put/index.ts b/src/routes/dossiers/campaignId/_put/index.ts index ed6f736ea..5cf416bcc 100644 --- a/src/routes/dossiers/campaignId/_put/index.ts +++ b/src/routes/dossiers/campaignId/_put/index.ts @@ -126,6 +126,96 @@ export default class RouteItem extends AdminRoute<{ .where({ id: this.campaignId, }); + + await this.linkRolesToCampaign(); + } + + private async linkRolesToCampaign() { + await this.cleanupCurrentRoles(); + const roles = this.getBody().roles; + if (!roles) return; + + await tryber.tables.CampaignCustomRoles.do().insert( + roles.map((role) => ({ + campaign_id: this.campaignId, + custom_role_id: role.role, + tester_id: role.user, + })) + ); + + await this.assignOlps(); + } + + private async cleanupCurrentRoles() { + const currentRoles = await tryber.tables.CampaignCustomRoles.do() + .select( + "tester_id", + "custom_role_id", + tryber.ref("olp").withSchema("custom_roles"), + "wp_user_id" + ) + .join( + "custom_roles", + "custom_roles.id", + "campaign_custom_roles.custom_role_id" + ) + .join( + "wp_appq_evd_profile", + "wp_appq_evd_profile.id", + "campaign_custom_roles.tester_id" + ) + .where({ + campaign_id: this.campaignId, + }); + if (!currentRoles.length) return; + await tryber.tables.CampaignCustomRoles.do().delete().where({ + campaign_id: this.campaignId, + }); + + for (const role of currentRoles) { + const olpObject = JSON.parse(role.olp); + await tryber.tables.WpAppqOlpPermissions.do() + .delete() + .where({ + main_id: this.campaignId, + main_type: "campaign", + wp_user_id: role.wp_user_id, + }) + .whereIn("type", olpObject); + } + } + + private async assignOlps() { + const roles = this.getBody().roles; + if (!roles) return; + + const roleOlps = await tryber.tables.CustomRoles.do() + .select("id", "olp") + .whereIn( + "id", + roles.map((role) => role.role) + ); + const wpUserIds = await tryber.tables.WpAppqEvdProfile.do() + .select("id", "wp_user_id") + .whereIn( + "id", + roles.map((role) => role.user) + ); + for (const role of roles) { + const olp = roleOlps.find((r) => r.id === role.role)?.olp; + const wpUserId = wpUserIds.find((r) => r.id === role.user); + if (olp && wpUserId) { + const olpObject = JSON.parse(olp); + await tryber.tables.WpAppqOlpPermissions.do().insert( + olpObject.map((olpType: string) => ({ + main_id: this.campaignId, + main_type: "campaign", + type: olpType, + wp_user_id: wpUserId.wp_user_id, + })) + ); + } + } } private getEndDate() { diff --git a/src/routes/dossiers/campaignId/_put/update.spec.ts b/src/routes/dossiers/campaignId/_put/update.spec.ts index c730ebf88..8fe6e5451 100644 --- a/src/routes/dossiers/campaignId/_put/update.spec.ts +++ b/src/routes/dossiers/campaignId/_put/update.spec.ts @@ -271,4 +271,127 @@ describe("Route POST /dossiers", () => { expect(campaign).toHaveProperty("os", "1,2"); expect(campaign).toHaveProperty("form_factor", "0,1"); }); + + describe("Role handling", () => { + beforeAll(async () => { + await tryber.tables.WpAppqEvdProfile.do().insert({ + id: 2, + wp_user_id: 2, + name: "Test User", + surname: "Test Surname", + education_id: 1, + employment_id: 1, + email: "", + }); + + await tryber.tables.CustomRoles.do().insert([ + { + id: 1, + name: "Test Role", + olp: '["appq_bugs"]', + }, + { + id: 2, + name: "Another Role", + olp: '["appq_bugs_2"]', + }, + ]); + }); + afterAll(async () => { + await tryber.tables.WpAppqEvdProfile.do().delete(); + await tryber.tables.CustomRoles.do().delete(); + await tryber.tables.WpAppqEvdProfile.do().delete(); + }); + afterEach(async () => { + await tryber.tables.CampaignCustomRoles.do().delete(); + await tryber.tables.WpAppqOlpPermissions.do().delete(); + }); + describe("When campaign has no roles", () => { + it("Should link the roles to the campaign", async () => { + const response = await request(app) + .put("/dossiers/1") + .set("authorization", "Bearer admin") + .send({ ...baseRequest, roles: [{ role: 1, user: 1 }] }); + + const id = response.body.id; + + const roles = await tryber.tables.CampaignCustomRoles.do() + .select() + .where({ campaign_id: id }); + expect(roles).toHaveLength(1); + expect(roles[0]).toHaveProperty("custom_role_id", 1); + expect(roles[0]).toHaveProperty("tester_id", 1); + }); + + it("Should set the olp roles to the campaign", async () => { + const response = await request(app) + .put("/dossiers/1") + .set("authorization", "Bearer admin") + .send({ ...baseRequest, roles: [{ role: 1, user: 2 }] }); + + const olps = await tryber.tables.WpAppqOlpPermissions.do() + .select() + .where({ main_id: 1 }); + expect(olps).toHaveLength(1); + expect(olps[0]).toHaveProperty("type", "appq_bugs"); + expect(olps[0]).toHaveProperty("main_type", "campaign"); + expect(olps[0]).toHaveProperty("wp_user_id", 2); + }); + }); + + describe("When campaign has roles", () => { + beforeEach(async () => { + await tryber.tables.CampaignCustomRoles.do().insert([ + { + campaign_id: 1, + custom_role_id: 1, + tester_id: 2, + }, + ]); + await tryber.tables.WpAppqOlpPermissions.do().insert([ + { + main_id: 1, + main_type: "campaign", + type: "appq_bugs", + wp_user_id: 2, + }, + ]); + }); + + afterEach(async () => { + await tryber.tables.CampaignCustomRoles.do().delete(); + await tryber.tables.WpAppqOlpPermissions.do().delete(); + }); + it("Should link the roles to the campaign", async () => { + const response = await request(app) + .put("/dossiers/1") + .set("authorization", "Bearer admin") + .send({ ...baseRequest, roles: [{ role: 2, user: 2 }] }); + + const id = response.body.id; + + const roles = await tryber.tables.CampaignCustomRoles.do() + .select() + .where({ campaign_id: id }); + expect(roles).toHaveLength(1); + expect(roles[0]).toHaveProperty("custom_role_id", 2); + expect(roles[0]).toHaveProperty("tester_id", 2); + }); + + it("Should set the olp roles to the campaign", async () => { + const response = await request(app) + .put("/dossiers/1") + .set("authorization", "Bearer admin") + .send({ ...baseRequest, roles: [{ role: 2, user: 2 }] }); + + const olps = await tryber.tables.WpAppqOlpPermissions.do() + .select() + .where({ main_id: 1 }); + expect(olps).toHaveLength(1); + expect(olps[0]).toHaveProperty("type", "appq_bugs_2"); + expect(olps[0]).toHaveProperty("main_type", "campaign"); + expect(olps[0]).toHaveProperty("wp_user_id", 2); + }); + }); + }); }); diff --git a/src/schema.ts b/src/schema.ts index 872e4ff6b..3fe50fd7e 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -971,6 +971,10 @@ export interface components { endDate?: string; deviceList: number[]; csm?: number; + roles?: { + role: number; + user: number; + }[]; }; }; }; @@ -4071,6 +4075,17 @@ export interface operations { id: number; name: string; }; + roles?: { + role?: { + id: number; + name: string; + }; + user?: { + id: number; + name: string; + surname: string; + }; + }[]; }; }; };