mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-28 23:01:37 +00:00
a12e43b624
The generic mutable-table blank template persisted incomplete company definitions before runtime domain validation. Seed valid company defaults, validate before SQL writes, and repair legacy blank entries.
258 lines
7.2 KiB
TypeScript
258 lines
7.2 KiB
TypeScript
import { readFileSync } from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
type ConfiguratorField = {
|
|
label: string
|
|
path: string
|
|
structure?: ConfiguratorStructure
|
|
type: string
|
|
value: unknown
|
|
}
|
|
|
|
type ConfiguratorStructure = {
|
|
entryDefault?: unknown
|
|
entries?: Array<{ structure: ConfiguratorStructure }>
|
|
fields?: Record<string, ConfiguratorStructure>
|
|
items?: ConfiguratorStructure[]
|
|
keyType?: 'number' | 'string'
|
|
kind: string
|
|
mutableKeys?: boolean
|
|
template?: ConfiguratorStructure
|
|
}
|
|
|
|
type ConfiguratorSection = {
|
|
fields: ConfiguratorField[]
|
|
id: string
|
|
scope: 'config' | 'media'
|
|
}
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const { loadConfiguratorSections } = require('./configurator-fixture.cjs') as {
|
|
loadConfiguratorSections: () => ConfiguratorSection[]
|
|
}
|
|
|
|
const configSource = readFileSync(
|
|
new URL('../../sky_phone/config/config.lua', import.meta.url),
|
|
'utf8',
|
|
)
|
|
|
|
function countStructure(structure: ConfiguratorStructure | undefined): number {
|
|
if (!structure) return 1
|
|
if (structure.fields) {
|
|
return Object.values(structure.fields).reduce(
|
|
(total, field) => total + countStructure(field),
|
|
0,
|
|
)
|
|
}
|
|
if (structure.items) {
|
|
return structure.items.reduce(
|
|
(total, item) => total + countStructure(item),
|
|
0,
|
|
)
|
|
}
|
|
if (structure.entries) {
|
|
return structure.entries.reduce(
|
|
(total, entry) => total + countStructure(entry.structure),
|
|
0,
|
|
)
|
|
}
|
|
return 1
|
|
}
|
|
|
|
describe('admin configurator fixture', () => {
|
|
it('exposes every SQL-managed config.lua root through the full live preview', () => {
|
|
const sections = loadConfiguratorSections()
|
|
const fields = sections.flatMap((section) => section.fields)
|
|
const roots = [
|
|
...configSource.matchAll(/^\s{0,4}Config\.([A-Za-z0-9_]+)\s*=/gm),
|
|
]
|
|
.map((match) => match[1])
|
|
.filter(
|
|
(root) =>
|
|
root !== 'Media' &&
|
|
root !== 'PhoneConfigurator' &&
|
|
root !== 'CommandPermissions',
|
|
)
|
|
|
|
expect(sections).toHaveLength(46)
|
|
expect(
|
|
fields.reduce(
|
|
(total, field) => total + countStructure(field.structure),
|
|
0,
|
|
),
|
|
).toBeGreaterThan(700)
|
|
for (const root of new Set(roots)) {
|
|
expect(
|
|
sections.some(
|
|
(section) =>
|
|
section.id === `config:${root}` ||
|
|
section.fields.some(
|
|
(field) =>
|
|
field.path === root || field.path.startsWith(`${root}.`),
|
|
),
|
|
),
|
|
`Missing Config.${root}`,
|
|
).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('preserves numeric Lua map keys and the false keybind option', () => {
|
|
const fields = loadConfiguratorSections().flatMap(
|
|
(section) => section.fields,
|
|
)
|
|
const darkChat = fields.find((field) => field.path === 'DarkChat')
|
|
const phone = fields.find((field) => field.path === 'Phone')
|
|
const companyJobs = fields.find(
|
|
(field) => field.path === 'Companies.Definitions',
|
|
)
|
|
const garage = fields.find((field) => field.path === 'Garage')
|
|
const timers = (darkChat?.value as Record<string, unknown> | undefined)
|
|
?.AllowedDisappearTimers
|
|
|
|
expect(timers).toMatchObject({
|
|
__skyType: 'map',
|
|
entries: expect.arrayContaining([
|
|
{ key: -1, keyType: 'number', value: true },
|
|
{ key: 0, keyType: 'number', value: true },
|
|
{ key: 604800, keyType: 'number', value: true },
|
|
]),
|
|
})
|
|
expect(darkChat?.structure?.fields?.AllowedDisappearTimers.kind).toBe('map')
|
|
expect(phone?.structure?.fields?.Keybind.kind).toBe('optionalString')
|
|
expect(companyJobs?.label).toBe('Jobs')
|
|
expect(companyJobs?.structure).toMatchObject({
|
|
entryDefault: {
|
|
AcceptsRequests: true,
|
|
Category: 'public_services',
|
|
Job: '',
|
|
ServiceLine: {
|
|
Number: '500',
|
|
Routing: 'round_robin',
|
|
},
|
|
Services: [
|
|
{
|
|
Id: '',
|
|
RequestsEnabled: true,
|
|
Title: '',
|
|
},
|
|
],
|
|
},
|
|
fields: {
|
|
ambulance: { kind: 'table' },
|
|
police: { kind: 'table' },
|
|
},
|
|
kind: 'table',
|
|
mutableKeys: true,
|
|
template: { kind: 'table' },
|
|
})
|
|
expect(
|
|
garage?.structure?.fields?.VehicleImages.fields?.ModelNames.kind,
|
|
).toBe('map')
|
|
})
|
|
|
|
it('exposes the requestable police assistance defaults', () => {
|
|
const companyJobs = loadConfiguratorSections()
|
|
.flatMap((section) => section.fields)
|
|
.find((field) => field.path === 'Companies.Definitions')
|
|
const police = (
|
|
companyJobs?.value as Record<string, Record<string, unknown>> | undefined
|
|
)?.police
|
|
|
|
expect(police).toMatchObject({
|
|
AcceptsRequests: true,
|
|
Emergency: true,
|
|
Services: [
|
|
{
|
|
Id: 'police-assistance',
|
|
RequestsEnabled: true,
|
|
},
|
|
],
|
|
})
|
|
expect(
|
|
companyJobs?.structure?.fields?.police.fields?.Services,
|
|
).toMatchObject({
|
|
items: [
|
|
{
|
|
fields: {
|
|
Id: { kind: 'value', valueType: 'string' },
|
|
RequestsEnabled: { kind: 'value', valueType: 'boolean' },
|
|
},
|
|
kind: 'table',
|
|
},
|
|
],
|
|
kind: 'list',
|
|
template: {
|
|
fields: {
|
|
Id: { kind: 'value', valueType: 'string' },
|
|
RequestsEnabled: { kind: 'value', valueType: 'boolean' },
|
|
},
|
|
kind: 'table',
|
|
},
|
|
})
|
|
})
|
|
|
|
it('publishes fixed schemas for every empty configurable collection', () => {
|
|
const fields = loadConfiguratorSections().flatMap(
|
|
(section) => section.fields,
|
|
)
|
|
const root = (path: string) =>
|
|
fields.find((field) => field.path === path)?.structure
|
|
|
|
expect(root('Music')?.fields?.Tracks).toMatchObject({
|
|
items: [],
|
|
kind: 'list',
|
|
template: {
|
|
fields: {
|
|
Artist: { kind: 'value', valueType: 'string' },
|
|
Id: { kind: 'value', valueType: 'string' },
|
|
Title: { kind: 'value', valueType: 'string' },
|
|
},
|
|
kind: 'table',
|
|
},
|
|
})
|
|
expect(root('FlipTok')?.fields?.MusicTracks.template).toMatchObject({
|
|
fields: { Url: { kind: 'value', valueType: 'string' } },
|
|
kind: 'table',
|
|
})
|
|
expect(root('Payphones')?.fields?.CustomLocations.template).toEqual({
|
|
kind: 'vector',
|
|
vectorType: 'vector4',
|
|
})
|
|
expect(root('CrewLink')?.fields?.ExternalPingResources).toMatchObject({
|
|
fields: {},
|
|
kind: 'table',
|
|
mutableKeys: true,
|
|
template: { kind: 'value', valueType: 'boolean' },
|
|
})
|
|
expect(root('CustomApps')?.fields?.TrustedAdapters).toMatchObject({
|
|
fields: {},
|
|
kind: 'table',
|
|
mutableKeys: true,
|
|
template: { kind: 'value', valueType: 'boolean' },
|
|
})
|
|
expect(
|
|
root('Garage')?.fields?.VehicleImages.fields?.ModelNames,
|
|
).toMatchObject({
|
|
entries: [],
|
|
keyType: 'number',
|
|
kind: 'map',
|
|
template: { kind: 'value', valueType: 'string' },
|
|
})
|
|
expect(
|
|
root('Companies.Definitions')?.fields?.ambulance.fields?.Services,
|
|
).toMatchObject({
|
|
items: [],
|
|
kind: 'list',
|
|
template: {
|
|
fields: {
|
|
Id: { kind: 'value', valueType: 'string' },
|
|
RequestsEnabled: { kind: 'value', valueType: 'boolean' },
|
|
},
|
|
kind: 'table',
|
|
},
|
|
})
|
|
})
|
|
})
|