Skip to content
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
152 changes: 152 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,155 @@ jobs:
files: ./fdm-data/coverage/coverage-final.json
flags: fdm-data
token: ${{ secrets.CODECOV_TOKEN }}

test-app:
name: app
# Containers must run in Linux based operating systems
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22]
permissions:
contents: read
packages: write
# Docker Hub image that `container-job` executes in
container: node:${{ matrix.node-version }}-bookworm-slim

# Service containers to run with `container-job`
services:
# Label used to access the service container
postgres:
# Docker Hub image with postgis extension
image: postgis/postgis:17-3.5
# Provide the password for postgres
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
# The hostname used to communicate with the PostgreSQL service container
POSTGRES_HOST: postgres
# The default PostgreSQL port
POSTGRES_PORT: 5432
# the default usernam
POSTGRES_USER: postgres
# the default password
POSTGRES_PASSWORD: postgres
# the default database
POSTGRES_DB: postgres

# Public name of the application displayed in the UI.
PUBLIC_FDM_NAME: MINAS4
# Base URL of the application (used for API calls, etc.).
PUBLIC_FDM_URL: http://localhost:3000
# URL to the privacy policy document.
PUBLIC_FDM_PRIVACY_URL: http://localhost:3000/privacy
# Secret key used to sign session cookies. MUST be a strong, random string.
FDM_SESSION_SECRET: blablabla1
# Authentication (Better Auth & OAuth Providers)
BETTER_AUTH_SECRET: blablabla2
# Full base URL of this application (used for redirects by better-auth).
BETTER_AUTH_URL: http://localhost:3000
# Mapbox API token for displaying maps.
PUBLIC_MAPBOX_TOKEN: ${{ secrets.PUBLIC_MAPBOX_TOKEN }}
# URL to the FlatGeobuf (.fgb) file containing selectable field geometries.
AVAILABLE_FIELDS_URL: https://storage.googleapis.com/fdm-public-data/fields/nl/2024/draft.fgb
steps:
# Include dependencies for codecov
- name: Install system dependencies
run: apt-get update && apt-get install -y git curl gpg

# Downloads a copy of the code in your repository before running CI tests
- name: Check out repository code
uses: actions/checkout@v4

- name: Cache turbo build setup
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-

- name: Setup pnpm 10
uses: pnpm/action-setup@v4
with:
version: 10.14.0

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
registry-url: 'https://npm.pkg.github.com'
cache: 'pnpm'

- name: Install Dependencies
run: pnpm i

- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps
working-directory: ./fdm-app

- name: Build fdm-data
run: pnpm build
working-directory: ./fdm-data

- name: Build fdm-core
run: pnpm build
working-directory: ./fdm-core

- name: Build fdm-calculator
run: pnpm build
working-directory: ./fdm-calculator

- name: Build fdm-app
run: pnpm build
working-directory: ./fdm-app

- name: Migrate database
run: pnpm db:migrate
working-directory: ./fdm-app

- name: Run login tests with coverage
run: pnpm test-login-ci
working-directory: ./fdm-app

- name: Move client coverage report for login tests
run: mv coverage/coverage-final.json coverage/coverage-final-login.json
working-directory: ./fdm-app

- name: Compile server coverage report
run: pnpm coverage-report-ci
working-directory: ./fdm-app

- name: Move server coverage report for login tests
run: mv coverage/coverage-final.json coverage/coverage-final-login-server.json
working-directory: ./fdm-app

- name: Run app tests with coverage
run: pnpm test-ci
working-directory: ./fdm-app

- name: Move client coverage report for app tests
run: mv coverage/coverage-final.json coverage/coverage-final-app.json
working-directory: ./fdm-app

- name: Compile server coverage report
run: pnpm coverage-report-ci
working-directory: ./fdm-app

- name: Move server coverage report for app tests
run: mv coverage/coverage-final.json coverage/coverage-final-app-server.json
working-directory: ./fdm-app

- name: fdm-app - Upload results to Codecov
uses: codecov/codecov-action@v5
with:
files: ./fdm-app/coverage/coverage-final*.json
flags: fdm-app
token: ${{ secrets.CODECOV_TOKEN }}
9 changes: 9 additions & 0 deletions fdm-app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@ node_modules
/build
/uploads
.env
.env.test
.env.production

dist
dist-ssr
playwright-report
test-results
test-tmp
coverage
*.local

.react-router/
# Sentry Config File
.env.sentry-build-plugin
21 changes: 20 additions & 1 deletion fdm-app/app/lib/email.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import { serverConfig } from "~/lib/config.server"
import type { ExtendedUser } from "~/types/extended-user"

const client = new postmark.ServerClient(String(process.env.POSTMARK_API_KEY))
const writeMagicLinkFile =
(process.env.CI && process.env.CI.length > 0) ||
(process.env.WRITE_MAGIC_LINK_FILE &&
process.env.WRITE_MAGIC_LINK_FILE.length > 0)
const sendRealEmail =
process.env.POSTMARK_API_KEY && process.env.POSTMARK_API_KEY.length > 0

interface Email {
From: string
Expand Down Expand Up @@ -141,14 +147,27 @@ function getTimeZoneFromUrl(url: string): string | undefined {
}

export async function sendEmail(email: Email): Promise<void> {
await client.sendEmail(email)
if (sendRealEmail) {
await client.sendEmail(email)
}
}

// Helper function to send magic link emails, to be passed to fdm-core
export async function sendMagicLinkEmailToUser(
emailAddress: string,
magicLinkUrl: string,
): Promise<void> {
if (writeMagicLinkFile) {
const testIo = await import("@/tests/test-io")
await testIo.writeTestFileLine(
testIo.magicLinkUrlFileName,
magicLinkUrl,
{
tmpUrl: testIo.runtimeTestTmpUrl(),
},
)
}

const email = await renderMagicLinkEmail(emailAddress, magicLinkUrl)
await sendEmail(email)
}
6 changes: 6 additions & 0 deletions fdm-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
"dotenvx": "dotenvx",
"start": "pnpm db:migrate && react-router-serve ./build/server/index.js",
"start-dev": "dotenvx run -- pnpm db:migrate && dotenvx run -- react-router-serve ./build/server/index.js",
"test": "dotenvx run -- playwright test -c ./playwright-login.config.ts & dotenvx run -- playwright test -c ./playwright-app.config.ts",
"test-login-ci": "playwright test -c ./playwright-login.config.ts",
"test-ci": "playwright test -c ./playwright-app.config.ts",
"coverage-report-ci": "c8 report -c v8-reporter.config.json",
"db:migrate": "node ./app/lib/fdm-migrate.server.js",
"typecheck": "react-router typegen && tsc"
},
Expand Down Expand Up @@ -82,6 +86,7 @@
},
"devDependencies": {
"@dotenvx/dotenvx": "catalog:",
"@playwright/test": "^1.54.1",
"@react-router/dev": "^7.9.1",
"@react-router/fs-routes": "^7.9.1",
"@svenvw/fdm-calculator": "workspace:*",
Expand All @@ -97,6 +102,7 @@
"@types/react-dom": "^19.1.9",
"@types/react-map-gl": "^6.1.7",
"@types/validator": "^13.15.3",
"c8": "^10.1.3",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.13",
"typescript": "catalog:",
Expand Down
53 changes: 53 additions & 0 deletions fdm-app/playwright-app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { defineConfig, devices } from "@playwright/test"

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests/app",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI ? [["dot"], ["json"]] : [["list"], ["json"]],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://localhost:3000",

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},

/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},

{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},

{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
],

/* Start the server in a CI environment such as GitHub Actions */
webServer: {
command:
"pnpm dotenvx run -- c8 -c v8-reporter.config.json react-router-serve ./build/server/index.js",
url: "http://localhost:3000",
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI,
},
})
46 changes: 46 additions & 0 deletions fdm-app/playwright-login.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { defineConfig, devices } from "@playwright/test"

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests/login",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI ? [["dot"], ["json"]] : [["list"], ["json"]],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://localhost:3000",

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},

/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],

/* Start the server in a CI environment such as GitHub Actions */
webServer: {
command:
"pnpm dotenvx run -- c8 -c v8-reporter.config.json react-router-serve ./build/server/index.js",
env: {
WRITE_MAGIC_LINK_FILE: "1",
},
url: "http://localhost:3000",
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI,
},
})
11 changes: 11 additions & 0 deletions fdm-app/tests/app/farm.$b_id_farm.fertilizers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { expect, test } from "@playwright/test"
import { loadSessionFromFile } from "../test-io"

test.beforeEach(async ({ page, context }) => {
await loadSessionFromFile(context)
await page.goto("/farm")
})

test.fixme("There is a set of default fertilizers", async ({ page }) => {
await expect(page.getByRole("table")).toBeVisible()
})
Loading
Loading