Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,6 @@ jobs:
env:
BUILD_MODE: production

- name: Truncate and seed database
run: pnpm run e2e:setup

- name: Run Playwright tests
uses: docker://mcr.microsoft.com/playwright:v1.58.0-jammy
with:
Expand Down
8 changes: 1 addition & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,7 @@ pnpm test path/to/test/file.e2e.test.ts
npx playwright install --with-deps
```

2. Run E2E setup:

```sh
pnpm run e2e:setup
```

3. Run E2E tests:
2. Run E2E tests:

```sh
pnpm run e2e:start
Expand Down
1 change: 0 additions & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"@react-email/components": "^1.0.10",
"@react-email/render": "^2.0.4",
"@sentry/node": "^10.46.0",
"@sindresorhus/slugify": "^3.0.0",
"@slack/bolt": "^4.6.0",
"ajv": "^8.18.0",
"amqplib": "^0.10.9",
Expand Down
8 changes: 6 additions & 2 deletions apps/backend/src/database/knex.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import Knex from "knex";

import config from "@/config";
import config, { type Config } from "@/config";
import { getKnexConfig } from "@/config/database";

import { transaction } from "./transaction";

export const knex = Knex(getKnexConfig(config));
function getKnexFromConfig(config: Config) {
return Knex(getKnexConfig(config));
}

export const knex = getKnexFromConfig(config);
transaction.knex(knex);
3 changes: 1 addition & 2 deletions apps/backend/src/database/models/Account.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { assertNever } from "@argos/util/assertNever";
import { invariant } from "@argos/util/invariant";
import { slugJsonSchema } from "@argos/util/slug";
import { memoize } from "lodash-es";
import type { Pojo, RelationMappings } from "objection";

import { slugJsonSchema } from "@/util/slug";

import { computeAdditionalScreenshots } from "../services/additional-screenshots";
import { Model } from "../util/model";
import { timestampsSchema } from "../util/schemas";
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/database/services/account.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ErrorCode } from "@argos/error-types";
import { assertNever } from "@argos/util/assertNever";
import { invariant } from "@argos/util/invariant";
import { slugify } from "@argos/util/slug";
import type { PartialModelObject, TransactionOrKnex } from "objection";

import { generateAuthEmailCode, verifyAuthEmailCode } from "@/auth/email";
Expand All @@ -10,7 +11,6 @@ import { sendNotification } from "@/notification";
import { getSlugFromEmail, sanitizeEmail } from "@/util/email";
import { boom } from "@/util/error";
import type { RequestLocation } from "@/util/request-location";
import { slugify } from "@/util/slug";

import { Account } from "../models/Account";
import { GithubAccount } from "../models/GithubAccount";
Expand Down
3 changes: 1 addition & 2 deletions apps/backend/src/database/services/team.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { assertNever } from "@argos/util/assertNever";
import { slugify } from "@argos/util/slug";
import type { PartialModelObject } from "objection";

import { slugify } from "@/util/slug";

import { Account } from "../models/Account";
import { Team } from "../models/Team";
import { TeamUser } from "../models/TeamUser";
Expand Down
9 changes: 7 additions & 2 deletions apps/backend/src/database/util/model.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { Knex } from "knex";
import { Model as ObjectionModel } from "objection";
import type { ModelOptions, QueryContext, TransactionOrKnex } from "objection";

import { knex } from "../knex";

ObjectionModel.knex(knex);

export class Model extends ObjectionModel {
id!: string;
createdAt!: string;
Expand All @@ -30,3 +29,9 @@ export class Model extends ObjectionModel {
return this;
}
}

function initObjection(knex: Knex) {
ObjectionModel.knex(knex);
}

initObjection(knex);
5 changes: 4 additions & 1 deletion apps/backend/src/graphql/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export type Context = BaseContext & {

async function getContextAuth(request: Request): Promise<AuthPayload | null> {
if (process.env["NODE_ENV"] === "test") {
return (request as any).__MOCKED_AUTH__ ?? null;
const mockedAuth = (request as any).__MOCKED_AUTH__;
if (mockedAuth) {
return mockedAuth;
}
}

return getAuthPayloadFromRequest(request);
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/util/email.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { slugify } from "./slug";
import { slugify } from "@argos/util/slug";

/**
* Sanitize email before inserting in database.
Expand Down
10 changes: 6 additions & 4 deletions apps/frontend/src/containers/Account/ChangeName.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useId } from "react";
import { useApolloClient } from "@apollo/client/react";
import { SubmitHandler, useForm } from "react-hook-form";

Expand Down Expand Up @@ -33,7 +34,8 @@ export const AccountChangeName = (props: {
title: React.ReactNode;
description: React.ReactNode;
}) => {
const { account } = props;
const { account, title, description } = props;
const headingId = useId();
const client = useApolloClient();
const form = useForm<Inputs>({
defaultValues: {
Expand All @@ -51,11 +53,11 @@ export const AccountChangeName = (props: {
form.reset(data);
};
return (
<Card>
<Card role="region" aria-labelledby={headingId}>
<Form form={form} onSubmit={onSubmit}>
<CardBody>
<CardTitle>{props.title}</CardTitle>
<CardParagraph>{props.description}</CardParagraph>
<CardTitle id={headingId}>{title}</CardTitle>
<CardParagraph>{description}</CardParagraph>
<FormTextInput
control={form.control}
{...form.register("name", {
Expand Down
13 changes: 8 additions & 5 deletions apps/frontend/src/containers/Account/ChangeSlug.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { useId } from "react";
import { useApolloClient } from "@apollo/client/react";
import { SLUG_REGEX } from "@argos/util/slug";
import { SubmitHandler, useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";

Expand Down Expand Up @@ -34,7 +36,8 @@ export const AccountChangeSlug = (props: {
title: React.ReactNode;
description: React.ReactNode;
}) => {
const { account } = props;
const { account, title, description } = props;
const headingId = useId();
const client = useApolloClient();
const form = useForm<Inputs>({
defaultValues: {
Expand All @@ -55,11 +58,11 @@ export const AccountChangeSlug = (props: {
});
};
return (
<Card>
<Card role="region" aria-labelledby={headingId}>
<Form form={form} onSubmit={onSubmit}>
<CardBody>
<CardTitle>{props.title}</CardTitle>
<CardParagraph>{props.description}</CardParagraph>
<CardTitle id={headingId}>{title}</CardTitle>
<CardParagraph>{description}</CardParagraph>
<FormTextInput
control={form.control}
{...form.register("slug", {
Expand All @@ -69,7 +72,7 @@ export const AccountChangeSlug = (props: {
message: "Account slugs must be 48 characters or less",
},
pattern: {
value: /^[-a-z0-9]+$/,
value: SLUG_REGEX,
message:
"Account slugs must be lowercase, begin with an alphanumeric character followed by more alphanumeric characters or dashes and ending with an alphanumeric character.",
},
Expand Down
13 changes: 5 additions & 8 deletions apps/frontend/src/ui/FormSuccess.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,18 @@ export function FormSuccess<
TFieldValues extends FieldValues = FieldValues,
TContext = any,
TTransformedValues = TFieldValues,
>({
className,
children,
isSuccessful,
...props
}: FormSuccessProps<TFieldValues, TContext, TTransformedValues>) {
const { control, ...rest } = props;
>(props: FormSuccessProps<TFieldValues, TContext, TTransformedValues>) {
const { className, children, isSuccessful, control, ...otherProps } = props;
const formState = useFormState({ control });
if (!isSuccessful && !formState.isSubmitSuccessful) {
return null;
}
return (
<div
className={clsx(className, "flex items-center gap-2 font-medium")}
{...rest}
role="alert"
aria-live="polite"
{...otherProps}
>
<CheckIcon className="text-success size-4" />
{children}
Expand Down
2 changes: 1 addition & 1 deletion knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@
}
}
},
"ignore": ["playwright.config.mts"],
"ignore": ["playwright.config.mts", "tests/seed.setup.ts"],
"ignoreBinaries": ["stripe", "ngrok", "op"]
}
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"dev": "turbo run watch-build watch-server watch-codegen --concurrency 100",
"build": "turbo run build",
"test": "TZ=utc vitest",
"e2e:setup": "NODE_ENV=test pnpm run --filter @argos/backend db:truncate && NODE_ENV=test pnpm run --filter @argos/backend db:seed",
"e2e:start": "NODE_ENV=test playwright test",
"setup": "turbo run setup",
"resend-webhook-proxy": "NODE_TLS_REJECT_UNAUTHORIZED=0 smee --url https://smee.io/H7XOiU60kSYJJTZs --target https://api.argos-ci.dev:4001/resend/event-handler",
Expand Down
Empty file.
3 changes: 2 additions & 1 deletion packages/util/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"devDependencies": {
"@argos/eslint-config": "workspace:*",
"@argos/tsconfig": "workspace:*",
"eslint": "catalog:"
"eslint": "catalog:",
"@sindresorhus/slugify": "^3.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { describe, expect, it } from "vitest";

import { slugify } from "./slug";
import { SLUG_REGEX, slugify } from "./slug";

describe("slugify", () => {
it('must slugify a string to match "^[-a-z0-9]+$" pattern', () => {
it("must slugify a string to match SLUG_REGEX pattern", () => {
expect(slugify("ControlPlane25")).toBe("control-plane25");
expect(slugify("&é!'lazepjfazehç")).toBe("and-e-lazepjfazehc");
expect(slugify("&é!'lazepjfazehç!")).toBe("and-e-lazepjfazehc");
expect(slugify("%a?eç,sq")).toBe("a-ec-sq");
expect(slugify("foo@239")).toBe("foo-239");
});

it("must give a generic slug when failing to generate one from the string", () => {
expect(slugify("淼淼自助店铺")).toMatch(/^[a-z-]+-\d+$/);
expect(slugify("淼淼自助店铺")).toMatch(SLUG_REGEX);
});
});
11 changes: 9 additions & 2 deletions apps/backend/src/util/slug.ts → packages/util/src/slug.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import baseSlugify from "@sindresorhus/slugify";

/**
* Regex to validate a slug.
* A slug must be lowercase alphanumeric, may contain hyphens in the middle,
* and must start and end with an alphanumeric character.
*/
export const SLUG_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;

/**
* Slug JSON schema
*/
export const slugJsonSchema = {
type: "string",
minLength: 1,
maxLength: 48,
pattern: "^[-a-z0-9]+$",
pattern: SLUG_REGEX.source,
};

/**
* Slugify a string to match "^[-a-z0-9]+$" pattern.
* Slugify a string to match the slug regex.
*/
export function slugify(str: string): string {
// Be sure the slug is not too long (max 48 chars)
Expand Down
20 changes: 9 additions & 11 deletions playwright.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import { defineConfig, devices } from "@playwright/test";
import argosConfig from "./apps/backend/src/config";

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
* Number of workers.
*/
// require('dotenv').config();
const WORKERS = 2;

/**
* @see https://playwright.dev/docs/test-configuration
Expand All @@ -31,8 +30,7 @@ const config = defineConfig({
forbidOnly: Boolean(process.env.CI),
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 2 : undefined,
workers: WORKERS,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
["list"],
Expand All @@ -56,16 +54,15 @@ const config = defineConfig({
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
},
name: "setup",
testMatch: /.*\.setup\.ts/,
},
{
name: "firefox",
name: "chromium",
use: {
...devices["Desktop Firefox"],
...devices["Desktop Chrome"],
},
dependencies: ["setup"],
},
],

Expand All @@ -79,6 +76,7 @@ const config = defineConfig({
timeout: 10 * 1000,
reuseExistingServer: false,
env: {
NODE_ENV: "test",
CSP_SCRIPT_SRC: `${getCSPScriptHash()},'unsafe-eval'`,
},
},
Expand Down
15 changes: 15 additions & 0 deletions playwright/.auth/user.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"cookies": [
{
"name": "argos_jwt",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoyLCJhY2NvdW50Ijp7ImlkIjoiMyIsIm5hbWUiOiJHcmVnIEJlcmfDqSIsInNsdWciOiJncmVnYmVyZ2UifSwiaWF0IjoxNzc1Mzc0NTMzLCJleHAiOjE3ODA1NTg1MzN9.uTA09_ZcO4EekwfAUGE-GbcFzShk1Cs18CXdtoQ34VM",
"domain": "localhost",
"path": "/",
"expires": -1,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
}
],
"origins": []
}
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading