Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ab): delete an experiment and cascade #29

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/backend/prisma/ab.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ model Variant {
experimentUuid String?
isControl Boolean @default(false)

Experiment Experiment? @relation(fields: [experimentUuid], references: [uuid])
Experiment Experiment? @relation(fields: [experimentUuid], references: [uuid], onDelete: Cascade)
VariantHit VariantHit[]
}

model VariantHit {
id Int @id @default(autoincrement())
date DateTime @default(now())
variant Variant @relation(fields: [variantUuid], references: [uuid])
variant Variant @relation(fields: [variantUuid], references: [uuid], onDelete: Cascade)
variantUuid String
}

model ExperimentEnvironment {
experiment Experiment @relation(fields: [experimentId], references: [uuid])
experiment Experiment @relation(fields: [experimentId], references: [uuid], onDelete: Cascade)
experimentId String
environment Environment @relation(fields: [environmentId], references: [uuid])
environmentId String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- DropForeignKey
ALTER TABLE "ExperimentEnvironment" DROP CONSTRAINT "ExperimentEnvironment_experimentId_fkey";

-- DropForeignKey
ALTER TABLE "Variant" DROP CONSTRAINT "Variant_experimentUuid_fkey";

-- DropForeignKey
ALTER TABLE "VariantHit" DROP CONSTRAINT "VariantHit_variantUuid_fkey";

-- AddForeignKey
ALTER TABLE "Variant" ADD CONSTRAINT "Variant_experimentUuid_fkey" FOREIGN KEY ("experimentUuid") REFERENCES "Experiment"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "VariantHit" ADD CONSTRAINT "VariantHit_variantUuid_fkey" FOREIGN KEY ("variantUuid") REFERENCES "Variant"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "ExperimentEnvironment" ADD CONSTRAINT "ExperimentEnvironment_experimentId_fkey" FOREIGN KEY ("experimentId") REFERENCES "Experiment"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;
14 changes: 14 additions & 0 deletions packages/backend/src/ab/ab.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
BadRequestException,
Body,
Controller,
Delete,
Get,
Param,
Post,
Expand All @@ -14,6 +15,8 @@ import { AbService } from './ab.service';
import { VariantAlreadyExists } from './errors';
import { VariantCreationSchema, VariantCreationDTO } from './experiment.dto';
import { HasExperimentAccess } from './guards/hasExperimentAccess';
import { Roles } from '../shared/decorators/Roles';
import { UserRoles } from '../users/roles';

@Controller()
export class AbController {
Expand Down Expand Up @@ -50,4 +53,15 @@ export class AbController {
throw e;
}
}

/**
* Delete an environment on a given project (by project id AND env id)
*/
@Delete('experiments/:experimentId')
@Roles(UserRoles.Admin)
@UseGuards(HasExperimentAccess)
@UseGuards(JwtAuthGuard)
deleteEnv(@Param('experimentId') experimentId: string) {
return this.abService.deleteExperiment(experimentId);
}
}
8 changes: 8 additions & 0 deletions packages/backend/src/ab/ab.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,12 @@ export class AbService {

return variant;
}

deleteExperiment(experimentId: string) {
return this.prisma.experiment.deleteMany({
where: {
uuid: experimentId,
},
});
}
}
72 changes: 72 additions & 0 deletions packages/backend/test/ab/ab.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,76 @@ describe('AbController (e2e)', () => {
});
});
});

describe('/experiments/1 (DELETE)', () => {
it('gives a 401 when the user is not authenticated', () =>
verifyAuthGuard(app, '/experiments/1', 'delete'));

it('gives a 403 when trying to access a valid project but an invalid env', async () => {
const access_token = await authenticate(app);

return request(app.getHttpServer())
.delete('/experiments/2')
.set('Authorization', `Bearer ${access_token}`)
.expect(403)
.expect({
statusCode: 403,
message: 'Forbidden resource',
error: 'Forbidden',
});
});

it('gives a 403 when the user requests a forbidden project', async () => {
const access_token = await authenticate(
app,
'jane.doe@gmail.com',
'password',
);

return request(app.getHttpServer())
.delete('/experiments/1')
.set('Authorization', `Bearer ${access_token}`)
.expect(403)
.expect({
statusCode: 403,
message: 'Forbidden resource',
error: 'Forbidden',
});
});

it('gives a 403 when the user is not allowed to perform the action', async () => {
const access_token = await authenticate(
app,
'john.doe@gmail.com',
'password',
);

return request(app.getHttpServer())
.delete('/experiments/1')
.set('Authorization', `Bearer ${access_token}`)
.expect(403)
.expect({
statusCode: 403,
message: 'Forbidden resource',
error: 'Forbidden',
});
});

it('gives a 200 when the user is allowed to perform the action', async () => {
const access_token = await authenticate(app);

const response = await request(app.getHttpServer())
.delete('/experiments/1')
.set('Authorization', `Bearer ${access_token}`);

expect(response.status).toBe(200);

// Make sure the user can't access the project anymore
const getResponse = await request(app.getHttpServer())
.get('/experiments/1')
.set('Authorization', `Bearer ${access_token}`);

expect(getResponse.status).toBe(403);
});
});
});