Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
4 changes: 3 additions & 1 deletion src/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ export default class PrismaDB implements PrismaInterface {
}

async getTestRun(runId: string) {
return this.prisma.testRun.findUnique({
const testRun = await this.prisma.testRun.findUnique({
where: { id: runId },
include: { testExecutions: true },
});
if (!testRun) throw new Error('Run not found.');
return testRun;
Comment thread
josephburgess marked this conversation as resolved.
}
}
102 changes: 80 additions & 22 deletions src/repository/repository.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,29 @@
import {
Organisation,
TestExecution,
TestRun,
TestStatus,
RunStatus,
WorkerStatus,
Executor,
Prisma,
Worker,
} from '@prisma/client';

import { S3Config, InputMaybe } from '../resolvers/types/generated';
import { Auth } from '../context.js';
import PrismaDB from '../db.js';
import {
OrganisationWithoutSlug,
S3CustomerConfig,
} from '../interfaces/prisma.js';
import { OrganisationWithoutSlug } from '../interfaces/prisma.js';

type GetBucketAndPathArgs = Auth | InputMaybe<S3Config> | undefined;

interface Repository {
getOrganisationFromApiKey: (key: string) => Promise<Organisation | null>;
createApiKey: (organisationId: string, name?: string) => Promise<string>;
getBucketAndPath: (args: GetBucketAndPathArgs) => S3CustomerConfig;
getOrganisationIdentifier: (args: GetBucketAndPathArgs) => string;
createOrganisation: (
args: OrganisationWithoutSlug,
) => Promise<Organisation | null>;
}

class PrismaRepository implements Repository {
db: PrismaDB = new PrismaDB();
class PrismaRepository {
private db: PrismaDB = new PrismaDB();

__construct() {
console.log('Starting PrismaRepository');
}

validateArgs(args: GetBucketAndPathArgs): Auth {
private validateArgs(args: GetBucketAndPathArgs): Auth {
const auth = args as Auth;

if (!auth || !auth.organisation) {
Expand Down Expand Up @@ -127,11 +117,20 @@ class PrismaRepository implements Repository {
rerunOf: true,
},
});
return execution?.rerunOf ?? null;
if (!execution) {
throw new Error('Test Execution not found.');
}
return execution.rerunOf ?? null;
}

async getTestExecutionById(id: string): Promise<TestExecution | null> {
return this.db.prisma.testExecution.findUnique({ where: { id } });
async getTestExecutionById(id: string): Promise<TestExecution> {
const testExecution = await this.db.prisma.testExecution.findUnique({
where: { id },
});
if (!testExecution) {
throw new Error('Test Execution not found.');
}
return testExecution;
}

async getRerunsByTestId(testExecutionId: string) {
Expand All @@ -158,15 +157,74 @@ class PrismaRepository implements Repository {
id: string,
status: RunStatus,
): Promise<TestRun | null> {
const updateData: Partial<Prisma.TestRunCreateInput> = { status };
if (status === RunStatus.COMPLETED) {
updateData.completedAt = new Date();
}

return this.db.prisma.testRun.update({
where: { id },
data: { status },
data: updateData,
});
}

async getTestRun(runId: string) {
return this.db.getTestRun(runId);
}

async createWorker(runId: string, executor: Executor) {
return this.db.prisma.worker.create({
data: {
status: WorkerStatus.PENDING,
executor,
testRunId: runId,
},
});
}

async getWorker(workerId: string) {
const worker = await this.db.prisma.worker.findUnique({
where: { id: workerId },
});
if (!worker) throw new Error('Worker not found.');
return worker;
}

async updateWorkerStatus(
workerId: string,
status: WorkerStatus,
): Promise<Worker> {
const updateData: Partial<Prisma.WorkerCreateInput> = {
status,
...(status === WorkerStatus.STARTED && { startedAt: new Date() }),
...(status === WorkerStatus.COMPLETED && {
completedAt: new Date(),
}),
};

return await this.db.prisma.worker.update({
where: { id: workerId },
data: updateData,
});
}

async getWorkersByRunId(runId: string) {
return this.db.prisma.worker.findMany({
where: { testRunId: runId },
});
}

async getTestExecutionsbyRunId(runId: string) {
return this.db.prisma.testExecution.findMany({
where: { testRunId: runId },
});
}

async getTestExecutionsByWorkerId(workerId: string) {
return this.db.prisma.testExecution.findMany({
where: { workerId },
});
}
}

export default new PrismaRepository();
106 changes: 88 additions & 18 deletions src/resolvers/Mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { v4 as uuidv4, v5 as uuidv5 } from 'uuid';
import {
TestStatus as PrismaTestStatus,
RunStatus as PrismaRunStatus,
WorkerStatus as PrismaWorkerStatus,
} from '@prisma/client';

import repository from '../repository/repository.js';
Expand All @@ -13,6 +14,9 @@ import {
TestStatus,
RunStatus,
TestExecutionStatus,
Worker,
WorkerStatus,
Executor,
CreateApiKeyResponse,
} from './types/generated.js';

Expand Down Expand Up @@ -69,6 +73,58 @@ const resolvers: MutationResolvers = {
})),
};
},

createWorkers: async (
_,
{ runID, count, executor },
{ repository },
): Promise<Worker[]> => {
const workerPromises = Array(count)
.fill(null)
.map(() => repository.createWorker(runID, executor));

const workers = await Promise.all(workerPromises);

return workers.map((worker) => ({
__typename: 'Worker',
...worker,
executor: executor,
status: WorkerStatus.Pending,
testExecutions: [],
}));
},

setWorkerStatus: async (
_,
{ workerID, status },
{ repository },
): Promise<Worker> => {
const worker = await repository.getWorker(workerID);

const isInputPending = status === WorkerStatus.Pending;
const isAlreadyCompleted = worker.status === WorkerStatus.Completed;
const isStartedAgain =
worker.status === WorkerStatus.Started &&
status === WorkerStatus.Started;

if (isInputPending || isAlreadyCompleted || isStartedAgain) {
throw new Error('Invalid status transition.');
}

const updatedWorker = await repository.updateWorkerStatus(
workerID,
status,
);

return {
__typename: 'Worker',
...updatedWorker,
executor: updatedWorker.executor as Executor,
status: updatedWorker.status as WorkerStatus,
testExecutions: [],
};
},

createTestExecution: async (
_,
{ runID, testName, featureFile, workerId },
Expand Down Expand Up @@ -125,9 +181,6 @@ const resolvers: MutationResolvers = {
const testExecution = await repository.getTestExecutionById(
testExecutionId,
);
if (!testExecution) {
throw new Error('Test Execution not found.');
}

const updatedTestStatus =
testStatus === TestStatus.Passed
Expand All @@ -141,21 +194,6 @@ const resolvers: MutationResolvers = {
until,
);

const run = await repository.getTestRun(testExecution.testRunId);
if (!run) {
throw new Error('Run not found.');
}
const allTestExecutionsCompleted = run.testExecutions.every(
(execution) =>
execution.result === PrismaTestStatus.PASSED ||
execution.result === PrismaTestStatus.FAILED,
);
if (allTestExecutionsCompleted) {
await repository.updateTestRunStatus(
testExecution.testRunId,
PrismaRunStatus.COMPLETED,
);
}
const { testName, featureFile, rerunOfId } = testExecution;
return {
__typename: 'TestExecutionStatus',
Expand All @@ -166,6 +204,38 @@ const resolvers: MutationResolvers = {
testStatus,
};
},

refreshRunStatus: async (
_,
{ runId },
{ repository },
): Promise<RunStatus> => {
const run = await repository.getTestRun(runId);

const workers = await repository.getWorkersByRunId(runId);
const allWorkersCompleted = workers.every(
(worker) => worker.status === PrismaWorkerStatus.COMPLETED,
);

if (!allWorkersCompleted) {
return run.status as RunStatus;
}

const updatedRun = await repository.updateTestRunStatus(
runId,
PrismaRunStatus.COMPLETED,
);

if (!updatedRun) {
console.error(
'Failed to update run status. Returning initial status.',
);
return run.status as RunStatus;
}

return updatedRun.status as RunStatus;
},

createApiKey: async (
_,
{ organisationId, name },
Expand Down
Loading