Skip to content
Merged
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
76 changes: 76 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Integration Tests

on:
push:
branches: [main]
pull_request:
workflow_dispatch:
inputs:
node-version:
description: 'Node.js version'
required: true
type: choice
default: 'all'
options:
- 'all'
- '22'
- '24'
- '26'

jobs:
generate-node-version-matrix:
Comment thread
BboyAkers marked this conversation as resolved.
name: Generate Node Version Matrix
runs-on: ubuntu-latest
outputs:
node-versions: ${{ steps.set-node-versions.outputs.node-versions }}

steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Set Node versions
id: set-node-versions
env:
NODE_VER: ${{ github.event.inputs.node-version }}
run: |
if [ "$NODE_VER" == "all" ] || [ -z "$NODE_VER" ]; then
echo "node-versions=[22, 24, 26]" >> $GITHUB_OUTPUT
else
echo "node-versions=[$NODE_VER]" >> $GITHUB_OUTPUT
fi

integration-tests:
name: Integration Tests (Node ${{ matrix.node-version }})
needs: [generate-node-version-matrix]
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
node-version: ${{ fromJSON(needs.generate-node-version-matrix.outputs.node-versions) }}

steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ matrix.node-version }}

- name: Install dependencies
run: npm ci

- name: Run integration tests
run: npm run test:integration
env:
HARPER_INTEGRATION_TEST_LOG_DIR: /tmp/harper-test-logs
FORCE_COLOR: '1'

- name: Upload Harper logs on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: harper-logs-node-${{ matrix.node-version }}
path: /tmp/harper-test-logs/
retention-days: 7
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This is a template for building [Harper](https://www.harper.fast/) applications.
To get started, make sure you have [installed Harper](https://docs.harperdb.io/docs/deployments/install-harper), which can be done quickly:

```sh
npm install -g harperdb
npm install -g harper
```

## Development
Expand All @@ -31,7 +31,7 @@ You should see the following:

Navigate to [http://localhost:9926](http://localhost:9926) in a browser and view the functional web application.

For more information about getting started with HarperDB and building applications, see our [getting started guide](https://docs.harperdb.io/docs).
For more information about getting started with Harper and building applications, see our [getting started guide](https://docs.harperdb.io/docs).

For more information on Harper Components, see the [Components documentation](https://docs.harperdb.io/docs/reference/components).

Expand Down
2 changes: 1 addition & 1 deletion config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# yaml-language-server: $schema=./node_modules/harperdb/config-app.schema.json
# yaml-language-server: $schema=./node_modules/harper/config-app.schema.json

# This is the configuration file for the application.
# It specifies built-in Harper components that will load the specified feature and files.
Expand Down
2 changes: 1 addition & 1 deletion graphql.config.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
schema: schema.graphql
include: node_modules/harperdb/schema.graphql
include: node_modules/harper/schema.graphql
documents: '**/*.graphql'
73 changes: 73 additions & 0 deletions integrationTests/app.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { suite, test, before, after } from 'node:test';
import { strictEqual, ok } from 'node:assert/strict';
import { setupHarperWithFixture, teardownHarper, type ContextWithHarper } from '@harperfast/integration-testing';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
import { createRequire } from 'node:module';

const __dirname = dirname(fileURLToPath(import.meta.url));
const FIXTURE_PATH = resolve(__dirname, '..');

// The `harper` package's `exports` map only exposes ".", so the harness's
// auto-resolution of 'harper/dist/bin/harper.js' fails with ERR_PACKAGE_PATH_NOT_EXPORTED.
// Resolve the CLI from the (exported) main entry and pass it explicitly.
const require = createRequire(import.meta.url);
const harperBinPath = resolve(dirname(require.resolve('harper')), 'bin/harper.js');

function authFetch(ctx: ContextWithHarper, path: string, init: RequestInit & { headers?: Record<string, string> } = {}) {
const { headers = {}, ...rest } = init;
const creds = Buffer.from(`${ctx.harper.admin.username}:${ctx.harper.admin.password}`).toString('base64');
return fetch(`${ctx.harper.httpURL}${path}`, { ...rest, headers: { Authorization: `Basic ${creds}`, ...headers } });
}

void suite('Application template', (ctx: ContextWithHarper) => {
before(async () => {
await setupHarperWithFixture(ctx, FIXTURE_PATH, { harperBinPath });
});

after(async () => {
await teardownHarper(ctx);
});

void test('Harper starts successfully', async () => {
const res = await authFetch(ctx, '/');
ok([200, 400, 404].includes(res.status), `Unexpected status ${res.status}`);
});

void test('GET /TableName/ returns an array', async () => {
const res = await authFetch(ctx, '/TableName/');
strictEqual(res.status, 200);
const body = await res.json();
ok(Array.isArray(body), 'expected array response');
});

void test('POST /TableName/ creates a record', async () => {
const res = await authFetch(ctx, '/TableName/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: 'test-1', name: 'Test Item', tag: 'test' }),
});
ok([200, 201, 204].includes(res.status), `expected successful create, got HTTP ${res.status}`);
});

void test('GET /TableName/:id returns the created record', async () => {
await authFetch(ctx, '/TableName/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: 'test-get', name: 'Get Test', tag: 'lookup' }),
});
const res = await authFetch(ctx, '/TableName/test-get');
strictEqual(res.status, 200);
const body = await res.json() as { id: string; name: string; tag: string };
strictEqual(body.name, 'Get Test');
});

void test('GET /Greeting returns hello world greeting', async () => {
const res = await authFetch(ctx, '/Greeting');
strictEqual(res.status, 200);
if (res.status === 200) {
const body = await res.json() as { greeting: string };
ok(body.greeting, 'expected greeting field');
}
});
});
Loading