mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-01 06:06:05 +08:00
smoketests
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
"postinstall": "nuxt prepare",
|
||||
"sharp:rebuild": "SHARP_IGNORE_GLOBAL_LIBVIPS=1 yarn rebuild sharp",
|
||||
"import:decision": "tsx --tsconfig tsconfig.scripts.json scripts/import-decision-tree.ts",
|
||||
"test": "tsx --tsconfig tsconfig.tests.json --test server/utils/openai.test.ts"
|
||||
"test": "tsx --tsconfig tsconfig.tests.json --test \"tests/**/*.test.ts\" \"server/**/*.test.ts\" \"shared/**/*.test.ts\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/image": "^2.0.0",
|
||||
|
||||
34
tests/server/validation.test.ts
Normal file
34
tests/server/validation.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { isValidEmail, validatePasswordStrength } from '~~/server/utils/validation'
|
||||
|
||||
describe('validation utils', () => {
|
||||
it('accepts valid email addresses and trims whitespace', () => {
|
||||
assert.equal(isValidEmail(' pilot@example.com '), true)
|
||||
assert.equal(isValidEmail('First.Last+tag@opsquawk.dev'), true)
|
||||
})
|
||||
|
||||
it('rejects malformed email addresses', () => {
|
||||
assert.equal(isValidEmail('pilot.example.com'), false)
|
||||
assert.equal(isValidEmail('pilot@localhost'), false)
|
||||
})
|
||||
|
||||
it('accepts strong passwords', () => {
|
||||
const result = validatePasswordStrength('Abflug1234!')
|
||||
assert.equal(result.valid, true)
|
||||
assert.equal(result.message, undefined)
|
||||
})
|
||||
|
||||
it('rejects passwords without special character', () => {
|
||||
const result = validatePasswordStrength('Abflug12345')
|
||||
assert.equal(result.valid, false)
|
||||
assert.match(result.message ?? '', /special character/i)
|
||||
})
|
||||
|
||||
it('rejects passwords with spaces', () => {
|
||||
const result = validatePasswordStrength('Abflug 1234!')
|
||||
assert.equal(result.valid, false)
|
||||
assert.match(result.message ?? '', /cannot contain spaces/i)
|
||||
})
|
||||
})
|
||||
40
tests/shared/radioSpeech.test.ts
Normal file
40
tests/shared/radioSpeech.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import {
|
||||
normalizeMetarPhrase,
|
||||
normalizeRadioPhrase,
|
||||
spellIcaoDigits,
|
||||
spellIcaoLetters,
|
||||
toIcaoPhonetic,
|
||||
} from '~~/shared/utils/radioSpeech'
|
||||
|
||||
describe('radioSpeech', () => {
|
||||
it('spells ICAO digits and letters', () => {
|
||||
assert.equal(spellIcaoDigits('120'), 'wun too zero')
|
||||
assert.equal(spellIcaoLetters('eddf'), 'Echo Delta Delta Foxtrot')
|
||||
assert.equal(toIcaoPhonetic('A3'), 'Alfa tree')
|
||||
})
|
||||
|
||||
it('normalizes a combined phrase with callsign, runway, frequency and taxi route', () => {
|
||||
const normalized = normalizeRadioPhrase('DLH359 contact 121.800, RWY 25R via N3 U4', {
|
||||
expandCallsigns: true,
|
||||
airlineMap: { DLH: 'Lufthansa' },
|
||||
})
|
||||
|
||||
assert.match(normalized, /Lufthansa tree fife niner/)
|
||||
assert.match(normalized, /wun too wun decimal eight zero zero/)
|
||||
assert.match(normalized, /runway too fife right/)
|
||||
assert.match(normalized, /November tree Uniform four/)
|
||||
})
|
||||
|
||||
it('normalizes SID suffix and METAR data', () => {
|
||||
const sid = normalizeRadioPhrase('MARUN 7F')
|
||||
const metar = normalizeMetarPhrase('EDDF 171450Z 28015G25KT 9999 -RA SCT025 BKN040 15/08 Q1013')
|
||||
|
||||
assert.equal(sid, 'MARUN seven Foxtrot')
|
||||
assert.match(metar, /wind too eight zero degrees/)
|
||||
assert.match(metar, /gusting too fife knots/)
|
||||
assert.match(metar, /QNH wun zero wun tree/)
|
||||
})
|
||||
})
|
||||
68
tests/shared/scenario.test.ts
Normal file
68
tests/shared/scenario.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import {
|
||||
altitudeToWords,
|
||||
createBaseScenario,
|
||||
createScenarioSeries,
|
||||
minutesToWords,
|
||||
runwayToWords,
|
||||
} from '~~/shared/learn/scenario'
|
||||
|
||||
describe('scenario helpers', () => {
|
||||
it('formats runway, minutes and altitude values', () => {
|
||||
assert.equal(runwayToWords('25R'), 'two fife right')
|
||||
assert.equal(minutesToWords(0), 'one minute')
|
||||
assert.equal(minutesToWords(4.4), 'four minutes')
|
||||
assert.equal(altitudeToWords(4500), 'four thousand fife zero zero')
|
||||
})
|
||||
|
||||
it('creates a base scenario with critical data fields', () => {
|
||||
const scenario = createBaseScenario()
|
||||
|
||||
assert.match(scenario.callsign, /^[A-Z]{3}\d{3,4}$/)
|
||||
assert.equal(scenario.metar.startsWith(`${scenario.airport.icao} `), true)
|
||||
assert.equal(scenario.frequencies.length > 0, true)
|
||||
assert.equal(typeof scenario.handoff.frequencyWords, 'string')
|
||||
assert.equal(scenario.handoff.frequencyWords.length > 0, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('createScenarioSeries', () => {
|
||||
it('returns cloned cached data and isolates mutations', () => {
|
||||
let sourceCalls = 0
|
||||
const series = createScenarioSeries(() => {
|
||||
sourceCalls += 1
|
||||
return {
|
||||
callsign: `SRC${sourceCalls}`,
|
||||
nested: { value: 1 },
|
||||
} as any
|
||||
})
|
||||
|
||||
const first = series()
|
||||
const second = series()
|
||||
|
||||
assert.equal(sourceCalls, 1)
|
||||
assert.notStrictEqual(first, second)
|
||||
assert.deepEqual(first, second)
|
||||
|
||||
first.nested.value = 99
|
||||
const third = series()
|
||||
assert.equal(third.nested.value, 1)
|
||||
})
|
||||
|
||||
it('supports override scenarios and reset', () => {
|
||||
const series = createScenarioSeries(() => ({ callsign: 'SRC1', nested: { value: 1 } } as any))
|
||||
|
||||
series.setScenario({ callsign: 'OVR1', nested: { value: 2 } } as any)
|
||||
const fromOverride = series()
|
||||
assert.equal(fromOverride.callsign, 'OVR1')
|
||||
|
||||
fromOverride.nested.value = 999
|
||||
assert.equal(series().nested.value, 2)
|
||||
|
||||
series.setScenario(null)
|
||||
series.reset()
|
||||
assert.equal(series().callsign, 'SRC1')
|
||||
})
|
||||
})
|
||||
39
tests/smoke/normalize.smoke.test.ts
Normal file
39
tests/smoke/normalize.smoke.test.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { atcReplyPrompt, atcSeedPrompt, atcSystemPrompt, normalizeATC } from '~~/server/utils/normalize'
|
||||
|
||||
describe('normalize smoke', () => {
|
||||
it('builds prompt templates with key context', () => {
|
||||
const systemPrompt = atcSystemPrompt({ regionHint: 'EUR' })
|
||||
const seedPrompt = atcSeedPrompt({
|
||||
airport: 'EDDF',
|
||||
aircraft: 'A320',
|
||||
type: 'IFR',
|
||||
stand: 'V155',
|
||||
dep: 'EHAM',
|
||||
runway: '25R',
|
||||
phase: 'taxi',
|
||||
})
|
||||
const replyPrompt = atcReplyPrompt('DLH359 ready for departure RWY 25R', {
|
||||
airport: 'EDDF',
|
||||
runway: '25R',
|
||||
phase: 'lineup',
|
||||
lastFreq: '121.800',
|
||||
})
|
||||
|
||||
assert.match(systemPrompt, /OUTPUT RULES \(STRICT\):/)
|
||||
assert.match(seedPrompt, /Airport EDDF/)
|
||||
assert.match(seedPrompt, /Phase: taxi/)
|
||||
assert.match(replyPrompt, /Pilot said: "DLH359 ready for departure RWY 25R"/)
|
||||
assert.match(replyPrompt, /Phase: lineup/)
|
||||
})
|
||||
|
||||
it('normalizes ICAO phrase fragments for TTS pipeline', () => {
|
||||
const normalized = normalizeATC('DLH359, taxi RWY 25R via N3 U4')
|
||||
|
||||
assert.match(normalized, /Lufthansa tree fife niner/)
|
||||
assert.match(normalized, /runway too fife right/)
|
||||
assert.match(normalized, /November tree Uniform four/)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user