Skip to content

Commit

Permalink
Tests and Types (and improvements to context passing on tests helpers)
Browse files Browse the repository at this point in the history
  • Loading branch information
fforres committed Aug 11, 2024
1 parent e0959ad commit 7e15348
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 6 deletions.
3 changes: 2 additions & 1 deletion src/schema/user/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,9 @@ builder.mutationField("retoolToken", (t) =>
const user = await ctx.DB.query.usersSchema.findFirst({
where: (u, { eq, and }) =>
and(
eq(u.email, userEmail.trim().toLocaleLowerCase()),
eq(u.email, userEmail),
eq(u.isRetoolEnabled, true),
eq(u.isEmailVerified, true),
eq(u.isSuperAdmin, true),
),
});
Expand Down
23 changes: 23 additions & 0 deletions src/schema/user/tests/token.generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-disable */
/* @ts-nocheck */
/* prettier-ignore */
/* This file is automatically generated using `npm run graphql:types` */
import type * as Types from '../../../generated/types';

import type { JsonObject } from "type-fest";
import gql from 'graphql-tag';
export type TokensMutationVariables = Types.Exact<{
input: Types.RetoolToken;
}>;


export type TokensMutation = { __typename?: 'Mutation', retoolToken: { __typename?: 'TokenRef', token: string } };


export const Tokens = gql`
mutation Tokens($input: retoolToken!) {
retoolToken(input: $input) {
token
}
}
`;
5 changes: 5 additions & 0 deletions src/schema/user/tests/token.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mutation Tokens($input: retoolToken!) {
retoolToken(input: $input) {

Check failure on line 2 in src/schema/user/tests/token.gql

View workflow job for this annotation

GitHub Actions / Linting and Typechecking

This field is marked as deprecated in your GraphQL schema (reason: Not enabled)
token
}
}
74 changes: 74 additions & 0 deletions src/schema/user/tests/token.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { faker } from "@faker-js/faker";
import { decode, verify } from "@tsndr/cloudflare-worker-jwt";
import { it, describe, assert } from "vitest";

import { USER } from "~/datasources/db/users";
import { executeGraphqlOperation, insertUser } from "~/tests/fixtures";

import {
Tokens,
TokensMutation,
TokensMutationVariables,
} from "./token.generated";

describe("User", () => {
it("Should create a token", async () => {
const retoolToken = faker.string.alphanumeric(10);
const encoder = faker.string.alphanumeric(10);
const user1 = await insertUser({
isRetoolEnabled: true,
isSuperAdmin: true,
isEmailVerified: true,
});
const response = await executeGraphqlOperation<
TokensMutation,
TokensMutationVariables
>(
{
document: Tokens,
variables: {
input: {
authToken: retoolToken,
userEmail: user1.email,
},
},
},
{
RETOOL_AUTHENTICATION_TOKEN: retoolToken,
SUPABASE_JWT_ENCODER: encoder,
},
);

assert.equal(response.errors, undefined);

const token = response.data?.retoolToken?.token;

assert.exists(token);

if (!token) {
throw new Error("Token not found");
}

const verified = await verify(token, encoder);

assert.exists(verified);

assert.equal(verified, true);

const decodedToken = decode<{
user_metadata: USER;
}>(token);

const userTokenData = decodedToken?.payload?.user_metadata;

assert.equal(userTokenData?.email, user1.email);

assert.equal(userTokenData?.isSuperAdmin, true);

assert.equal(userTokenData?.isRetoolEnabled, true);

assert.equal(userTokenData?.isEmailVerified, true);

assert.equal(userTokenData?.id, user1.id);
});
});
19 changes: 14 additions & 5 deletions src/tests/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import {
import { defaultLogger } from "~/logging";
import { schema } from "~/schema";
import { getTestDB } from "~/tests/fixtures/databaseHelper";
import { Context } from "~/types";

const insertUserRequest = insertUsersSchema.deepPartial();

Expand All @@ -121,7 +122,10 @@ const CRUDDates = ({
deletedAt: typeof deletedAt !== "undefined" ? deletedAt : faker.date.recent(),
});

const createExecutor = (user?: Awaited<ReturnType<typeof insertUser>>) =>
const createExecutor = (
user?: Awaited<ReturnType<typeof insertUser>> | undefined,
context?: Partial<Context>,
) =>
buildHTTPExecutor({
fetch: createYoga<Env>({
schema,
Expand All @@ -134,6 +138,7 @@ const createExecutor = (user?: Awaited<ReturnType<typeof insertUser>>) =>
logger: defaultLogger,
USER: user ? user : undefined,
GET_STRIPE_CLIENT: () => null,
...(context ?? {}),
};
},
plugins: [authZEnvelopPlugin({ rules })],
Expand All @@ -145,8 +150,9 @@ export const executeGraphqlOperation = <
TVariables extends Record<string, any> = Record<string, any>,
>(
params: ExecutionRequest<TVariables, unknown, unknown, undefined, unknown>,
context?: Partial<Context>,
): Promise<ExecutionResult<TResult>> => {
const executor = createExecutor();
const executor = createExecutor(undefined, context);

// @ts-expect-error This is ok. Executor returns a promise with they types passed
return executor(params);
Expand All @@ -158,8 +164,9 @@ export const executeGraphqlOperationAsUser = <
>(
params: ExecutionRequest<TVariables, unknown, unknown, undefined, unknown>,
user: Awaited<ReturnType<typeof insertUser>>,
context?: Partial<Context>,
): Promise<ExecutionResult<TResult>> => {
const executor = createExecutor(user);
const executor = createExecutor(user, context);

// @ts-expect-error This error is ok. Executor returns a promise with they types passed
return executor(params);
Expand All @@ -171,13 +178,14 @@ export const executeGraphqlOperationAsSuperAdmin = async <
>(
params: ExecutionRequest<TVariables, unknown, unknown, undefined, unknown>,
user?: Awaited<ReturnType<typeof insertUser>>,
context?: Partial<Context>,
): Promise<ExecutionResult<TResult>> => {
if (user && !user.isSuperAdmin) {
throw new Error("User passed is not a super admin");
}

const superAdmin = user ?? (await insertUser({ isSuperAdmin: true }));
const executor = createExecutor(superAdmin);
const executor = createExecutor(superAdmin, context);

// @ts-expect-error This error is ok. Executor returns a promise with they types passed
return executor(params);
Expand Down Expand Up @@ -234,10 +242,11 @@ export const insertUser = async (
externalId: partialInput?.externalId ?? faker.string.uuid(),
username: partialInput?.username ?? faker.internet.userName(),
bio: partialInput?.bio ?? faker.lorem.paragraph(),
email: partialInput?.email ?? faker.internet.email(),
email: (partialInput?.email ?? faker.internet.email()).toLowerCase(),
name: partialInput?.name,
isSuperAdmin: partialInput?.isSuperAdmin,
isEmailVerified: partialInput?.isEmailVerified,
isRetoolEnabled: partialInput?.isRetoolEnabled,
pronouns:
partialInput?.pronouns ??
faker.helpers.arrayElement(Object.values(PronounsEnum)),
Expand Down

0 comments on commit 7e15348

Please sign in to comment.