Skip to content
ENC Protocol

5. Write a test

Test the app the way it runs — against a real node, with vitest. A global setup boots your node (hook and all); the tests sign as the owner, mint the enclaves, write, and read back over the node's ECDH-authenticated query path.

npm install -D vitest

vitest.config.mjs:

vitest.config.mjs
import { defineConfig } from 'vitest/config'
 
export default defineConfig({
  test: { globalSetup: './test/global-node.mjs', hookTimeout: 120_000, testTimeout: 60_000 },
})

test/global-node.mjs — boots the node with your OWNER_PUBKEY so its admission hook admits the test's owner identity (and reuses a node if one's already up):

test/global-node.mjs
import { spawn } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import { dirname, resolve } from 'node:path'
import { readFileSync } from 'node:fs'
import { ownerFromMnemonic } from '../owner.mjs'
 
const __dirname = dirname(fileURLToPath(import.meta.url))
const NODE_DIR = process.env.NODE_DIR || resolve(__dirname, '../../impl-node')
const PORT = Number(process.env.NODE_PORT || 8787)
const BASE = `http://localhost:${PORT}/`
const ownerPub = ownerFromMnemonic(readFileSync(resolve(__dirname, '../owner.seed'), 'utf8')).publicKeyHex
 
export default async function () {
  try { if ((await fetch(BASE)).ok) return () => {} } catch {}     // reuse a running node
  const node = spawn(
    'npx', ['wrangler', 'dev', '--config', 'test/wrangler.toml', '--local',
      '--port', String(PORT), '--var', `OWNER_PUBKEY:${ownerPub}`],
    { cwd: NODE_DIR, stdio: 'ignore', detached: true },
  )
  for (let i = 0; i < 90; i++) {
    await new Promise((r) => setTimeout(r, 1000))
    try { if ((await fetch(BASE)).ok) return async () => { try { process.kill(-node.pid) } catch {} } } catch {}
  }
  throw new Error(`ENC node did not start at ${BASE}`)
}

personal.test.mjs — sign as the owner (the only identity the hook admits):

personal.test.mjs
import { test, expect } from 'vitest'
import { PersonalSdk } from '@enc-protocol/personal-cli'
import { flattenEnclaveManifest } from '@enc-protocol/protocol-runtime'
import { NetworkAdapter } from '@enc-protocol/client/network-adapter.js'
import { ownerFromMnemonic } from './owner.mjs'
import { personal } from './personal.manifest.mjs'   // the manifest you authored in step 3
import { readFileSync } from 'node:fs'
 
const nodeUrl = process.env.NODE_URL || 'http://localhost:8787'
const owner = ownerFromMnemonic(readFileSync('owner.seed', 'utf8'))
 
// Build a PersonalSdk signed by the owner key (Node-side; the browser uses the
// wallet). Minting is idempotent, so each test resolves the same enclave.
async function ownerSdk() {
  const wire = flattenEnclaveManifest(personal).enclaveManifest(owner.publicKeyHex)
  const adapter = new NetworkAdapter(nodeUrl, '', owner)
  await adapter.createEnclave(wire)
  const sdk = new PersonalSdk({ adapter, identity: { pubHex: owner.publicKeyHex } })
  await sdk.init()
  return sdk
}
 
test('public post round-trips on a real node', async () => {
  const sdk = await ownerSdk()
  await sdk.submitPublic({ draft: 'gm everyone' })
  const posts = await sdk.queryPublic()
  expect(posts.map((p) => JSON.parse(p.content).draft)).toContain('gm everyone')
})
 
test('private notes round-trip for the owner', async () => {
  const sdk = await ownerSdk()
  await sdk.submitPrivate({ draft: 'a private note' })
  const notes = await sdk.queryPrivate()
  expect(notes.map((n) => JSON.parse(n.content).draft)).toContain('a private note')
})

Run them:

$ npx vitest run
 Test Files  1 passed (1)
      Tests  2 passed (2)

The tests sign real commits with your owner key and read them back through the node's ECDH-authenticated query path — the same protocol your app exercises through the wallet, against a real node, not a mock.

Next: Deploy to production →