Skip to content

Commit

Permalink
chore: new test -d
Browse files Browse the repository at this point in the history
  • Loading branch information
arpowers committed Mar 11, 2024
1 parent 0c91936 commit bd2f74c
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 34 deletions.
6 changes: 6 additions & 0 deletions @fiction/core/plugin-env/cliProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,9 @@ process.on('uncaughtException', (error) => {
logger.error('Uncaught error!', { error })
exitHandler({ exit: true, code: 1 })
})

// untested, but should be tried when process.on is overridden in tests
process.on('message', (msg) => {
if (msg === 'shutdown')
exitHandler({ exit: true, shutdown: true })
})
64 changes: 49 additions & 15 deletions @fiction/core/test-utils/buildTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,21 @@ export async function createTestServer(params: {
}
}

function getModifiedCommands(commands: CliCommand[]) {
const modifiedCommands = commands.map((command) => {
if (command.port.value)
command.port.value = randomBetween(1000, 30000)

return command
})

const portOptions = commands.filter(_ => _.port.value).map((_) => {
return `--${_.command}-port ${String(_.port.value)}`
})

return { commands: modifiedCommands, portOptions }
}

export async function appBuildTests(config: {
moduleName?: string
cwd?: string
Expand All @@ -133,21 +148,6 @@ export async function appBuildTests(config: {
if (!cwd)
throw new Error('cwd is not defined')

const getModifiedCommands = (commands: CliCommand[]) => {
const modifiedCommands = commands.map((command) => {
if (command.port.value)
command.port.value = randomBetween(1000, 30000)

return command
})

const portOptions = commands.filter(_ => _.port.value).map((_) => {
return `--${_.command}-port ${String(_.port.value)}`
})

return { commands: modifiedCommands, portOptions }
}

const logger = log.contextLogger('BUILD TESTS')
const BUILD_TIMEOUT = 180_000

Expand Down Expand Up @@ -234,3 +234,37 @@ export async function appBuildTests(config: {
}, BUILD_TIMEOUT)
})
}

export async function appRunTest(config: {
cmd: string
port: number
}): Promise<{ html: string, status: number }> {
const { cmd, port } = config

const BUILD_TIMEOUT = 60_000

const command = [cmd].join(' ')

let html = ''
let status = 0

await executeCommand({
command,
envVars: { IS_TEST: '1', TEST_ENV: 'unit' },
timeout: BUILD_TIMEOUT,
triggerText: '[ready]',
onTrigger: async (args) => {
try {
const response = await fetch(`http://localhost:${port}/`)
html = await response.text()
status = response.status
}
finally {
// This ensures the server is closed even if the assertions above fail
await args.close()
}
},
})

return { html, status }
}
50 changes: 32 additions & 18 deletions @fiction/core/utils/nodeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import * as mod from 'node:module'
import { Buffer } from 'node:buffer'
import process from 'node:process'
import fs from 'fs-extra'
import type { ExecaChildProcess } from 'execa'
import { execaCommand } from 'execa'
import type { PackageJson } from '../types'
import { isNode } from './vars'
import { waitFor } from './utils'

interface WhichModule {
moduleName?: string
Expand All @@ -18,35 +20,27 @@ export async function executeCommand(args: {
envVars?: { [key: string]: string }
timeout?: number
resolveText?: string
triggerText?: string
onTrigger?: (args: { stdout: string, stderr: string, text: string, close: () => Promise<void>, cp: ExecaChildProcess }) => Promise<void>
}) {
const { command, envVars = {}, timeout = 10000, resolveText } = args
const output: string[] = []
const errorsOutput: string[] = []

const cp = execaCommand(command, { env: envVars, timeout })

const commandDetails = () => ({ stdout: output.join(`\n`), stderr: errorsOutput.join(`\n`) })

try {
await new Promise((resolve, reject) => {
const cp = execaCommand(command, { env: envVars, timeout })

cp.stdout?.pipe(process.stdout)
cp.stderr?.pipe(process.stderr)

const resolveOnText = (text: string) => {
if (resolveText && text.includes(resolveText)) {
resolve(text)
cp.kill('SIGTERM', { forceKillAfterTimeout: 5000 })
}
const close = async () => {
cp.kill('SIGTERM', { forceKillAfterTimeout: 5000 })
resolve(1)
}

cp.stdout?.on('data', (d: Buffer) => {
output.push(d.toString())
resolveOnText(d.toString())
})

cp.stderr?.on('data', (d: Buffer) => {
errorsOutput.push(d.toString())
resolveOnText(d.toString())
})

void cp.on('close', (code) => {
if (code === 0)
resolve(output.join(`\n`))
Expand All @@ -57,14 +51,34 @@ export async function executeCommand(args: {
void cp.on('error', (err) => {
reject(err)
})

const onText = async (text: string) => {
if (resolveText && text.includes(resolveText)) {
await close()
resolve(text)
}

if (args.triggerText && text.includes(args.triggerText) && args.onTrigger)
await args.onTrigger({ ...commandDetails(), text, close, cp })
}

cp.stdout?.on('data', async (d: Buffer) => {
output.push(d.toString())
await onText(d.toString())
})

cp.stderr?.on('data', async (d: Buffer) => {
errorsOutput.push(d.toString())
await onText(d.toString())
})
})
}
catch (error) {
console.error('The command failed:', error)
throw error // Rethrow the error to be handled by the caller
}

return { stdout: output.join(`\n`), stderr: errorsOutput.join(`\n`) }
return commandDetails()
}

export function getRequire() {
Expand Down
16 changes: 15 additions & 1 deletion @fiction/www/test/builtFiles.dist.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'
import fs from 'fs-extra'
import { safeDirname } from '@fiction/core'
import { randomBetween, safeDirname } from '@fiction/core'
import { appRunTest } from '@fiction/core/test-utils/buildTest'

describe('files built', () => {
const dir = safeDirname(import.meta.url, '..')
Expand Down Expand Up @@ -30,3 +31,16 @@ describe('files built', () => {
expect(serverDirFiles).toEqual(expect.arrayContaining(serverFiles))
})
})

describe('serving built files', async () => {
it('runs app', async () => {
const appPort = randomBetween(1000, 30000)
const sitesPort = randomBetween(1000, 30000)
const r = await appRunTest({
cmd: `npm exec -w @fiction/www -- fiction run app --app-port=${appPort} --sites-port=${sitesPort}`,
port: appPort,
})
expect(r.status).toBe(200)
expect(r.html).toContain('<!DOCTYPE html>')
})
})

0 comments on commit bd2f74c

Please sign in to comment.