Files
aurora-dockside/src/main/native/nativeProject.test.ts
T

205 lines
7.3 KiB
TypeScript
Raw Normal View History

import { execFile, spawn } from 'child_process'
import { createRequire } from 'module'
import { mkdtemp, readFile, writeFile } from 'fs/promises'
import { tmpdir } from 'os'
2026-08-26 02:31:16 -05:00
import { join, resolve } from 'path'
import { promisify } from 'util'
2026-08-22 03:47:09 -05:00
import { describe, expect, it } from 'vitest'
import {
renderMariaDbConfig,
2026-08-22 04:26:37 -05:00
renderNativeAdminerBootstrap,
renderNginxConfig,
renderPhpFpmConfig,
startNativeProject,
stopNativeProject
} from './nativeProject'
import { allocateNativePorts } from './portAllocator'
2026-08-22 03:47:09 -05:00
2026-08-26 02:31:16 -05:00
const projectRoot = join(tmpdir(), 'aurora demo')
const installedRuntimeRoot = join(tmpdir(), 'aurora-runtime')
2026-08-22 03:47:09 -05:00
const project = {
name: 'demo',
2026-08-26 02:31:16 -05:00
root: projectRoot,
2026-08-22 03:47:09 -05:00
docroot: 'public',
ports: { http: 41001, php: 41002, database: 41003, node: 41004 }
}
const execFileAsync = promisify(execFile)
2026-08-22 03:47:09 -05:00
function runWithInput(command: string, args: string[], cwd: string, input: string): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { cwd, env: process.env, stdio: ['pipe', 'pipe', 'pipe'] })
let stderr = ''
child.stderr.on('data', (chunk) => {
stderr += chunk.toString()
})
child.on('error', reject)
child.on('close', (code) => {
if (code === 0) resolve()
else reject(new Error(stderr.trim() || `${command} exited with code ${code}`))
})
child.stdin.end(input)
})
}
2026-08-22 03:47:09 -05:00
describe('native project configuration', () => {
it('isolates PHP-FPM on its allocated loopback port', () =>
expect(renderPhpFpmConfig(project)).toContain('listen = 127.0.0.1:41002'))
it('routes nginx PHP requests to the project PHP-FPM service', () => {
const config = renderNginxConfig(project)
expect(config).toContain('listen 127.0.0.1:41001')
expect(config).toContain('fastcgi_pass 127.0.0.1:41002')
expect(config).toContain('location ~ \\.php$')
2026-08-26 02:31:16 -05:00
expect(config).toContain(resolve(projectRoot, 'public').replace(/\\/g, '\\\\'))
2026-08-22 03:47:09 -05:00
})
it('isolates MariaDB data and networking', () => {
2026-08-26 02:31:16 -05:00
const config = renderMariaDbConfig(project, installedRuntimeRoot)
2026-08-22 03:47:09 -05:00
expect(config).toContain('bind-address=127.0.0.1')
expect(config).toContain('port=41003')
2026-08-26 02:31:16 -05:00
expect(config).toContain(join(projectRoot, '.aurora', 'native', 'data', 'mariadb'))
2026-08-22 03:47:09 -05:00
})
2026-08-22 04:26:37 -05:00
it('creates a loopback Adminer endpoint with project database credentials', () => {
expect(renderNginxConfig(project)).toContain('location = /__aurora/adminer/')
const bootstrap = renderNativeAdminerBootstrap({
...project,
2026-08-26 02:31:16 -05:00
installedRuntimeRoot
2026-08-22 04:26:37 -05:00
})
expect(bootstrap).toContain("value = '127.0.0.1:41003'")
expect(bootstrap).toContain("username.value = 'db'")
2026-08-26 21:04:42 -05:00
const adminer =
process.platform === 'win32'
? join(installedRuntimeRoot, 'tools', 'adminer.php')
: join(installedRuntimeRoot, 'root', 'usr', 'share', 'aurora', 'adminer.php')
expect(bootstrap).toContain(adminer.replace(/\\/g, '\\\\'))
2026-08-22 04:26:37 -05:00
})
2026-08-22 03:47:09 -05:00
})
it.runIf(Boolean(process.env.AURORA_NATIVE_SMOKE_ROOT))(
'serves PHP through the complete native stack',
async () => {
const root = await mkdtemp(join(tmpdir(), 'aurora-native-project-'))
await writeFile(join(root, 'index.php'), '<?php echo "aurora-native-ok";')
const definition = {
name: `smoke-${Date.now()}`,
root,
docroot: '',
ports: await allocateNativePorts(),
installedRuntimeRoot: process.env.AURORA_NATIVE_SMOKE_ROOT
}
try {
await startNativeProject(definition)
const response = await fetch(`http://127.0.0.1:${definition.ports.http}`)
expect(await response.text()).toBe('aurora-native-ok')
} finally {
await stopNativeProject(definition)
}
},
45000
)
it.runIf(Boolean(process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT))(
'provisions WordPress with the bundled native runtime',
async () => {
const runtime = process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT!
const root = await mkdtemp(join(tmpdir(), 'aurora-native-wordpress-'))
const definition = {
name: `wordpress-${Date.now()}`,
root,
docroot: '',
ports: await allocateNativePorts(),
installedRuntimeRoot: runtime
}
const wordpress = createRequire(import.meta.url)(
'../../../packages/aurora-module-wordpress/main/index.cjs'
) as { projectCreate: (context: Record<string, unknown>) => Promise<void> }
try {
await wordpress.projectCreate({
moduleId: 'wordpress',
directory: root,
projectName: definition.name,
settings: {
title: 'Aurora native smoke',
admin_user: 'aurora-admin',
admin_password: 'aurora-native-test-password',
admin_email: '[email protected]',
locale: 'en_US',
multisite: 'none',
wp_debug: false
},
environment: { runtimeEngine: 'native' },
urls: {
http: `http://127.0.0.1:${definition.ports.http}`,
https: `http://127.0.0.1:${definition.ports.http}`
},
native: {
php: join(runtime, 'bin', 'php'),
wp: join(runtime, 'bin', 'wp'),
databasePort: definition.ports.database,
start: () => startNativeProject(definition)
},
run: async (_label: string, command: string, args: string[]) => {
await execFileAsync(command, args, {
cwd: root,
env: process.env,
maxBuffer: 32 * 1024 * 1024
})
},
setProjectMetadata: async () => undefined,
saveCredentials: async () => undefined
})
expect(await readFile(join(root, 'wp-config.php'), 'utf8')).toContain(
`127.0.0.1:${definition.ports.database}`
)
const response = await fetch(`http://127.0.0.1:${definition.ports.http}`)
expect(response.ok).toBe(true)
expect(await response.text()).toContain('Aurora native smoke')
const databaseArgs = [
'--host=127.0.0.1',
`--port=${definition.ports.database}`,
'--user=db',
'--password=db',
'db'
]
const dump = await execFileAsync(
join(runtime, 'bin', 'mariadb-dump'),
[...databaseArgs.slice(0, -1), '--single-transaction', 'db'],
{ cwd: root, env: process.env, maxBuffer: 32 * 1024 * 1024 }
)
await execFileAsync(
join(runtime, 'bin', 'mariadb'),
[
...databaseArgs,
'-e',
"UPDATE wp_options SET option_value='Changed' WHERE option_name='blogname'"
],
{ cwd: root, env: process.env }
)
await runWithInput(join(runtime, 'bin', 'mariadb'), databaseArgs, root, dump.stdout)
const restored = await execFileAsync(
join(runtime, 'bin', 'mariadb'),
[
...databaseArgs,
'--batch',
'--skip-column-names',
'-e',
"SELECT option_value FROM wp_options WHERE option_name='blogname'"
],
{ cwd: root, env: process.env }
)
expect(restored.stdout.trim()).toBe('Aurora native smoke')
2026-08-22 04:26:37 -05:00
const adminerResponse = await fetch(
`http://127.0.0.1:${definition.ports.http}/__aurora/adminer/`
)
const adminerHtml = await adminerResponse.text()
expect(adminerResponse.ok).toBe(true)
expect(adminerHtml).toContain('Adminer')
expect(adminerHtml).toContain("username.value = 'db'")
expect(adminerHtml).toContain("value = '127.0.0.1:")
} finally {
await stopNativeProject(definition)
}
},
120000
)