Compare commits

..

1 Commits

Author SHA1 Message Date
Alec Schitzkat f654d82b4b FIX - Improve test data idempotency and reliability
Refactor test data seeding to use deterministic identifiers for DarkChat and Flare, ensuring re-runs don't create duplicate entries or inconsistent states. Implement robust SIM card movement logic and add contract tests to verify seeding constraints and default configuration safety.
2026-08-19 19:31:39 +02:00
7 changed files with 14 additions and 135 deletions
@@ -17,9 +17,7 @@ describe('PhoneSetupAssistant contract', () => {
expect(source).toContain('WALLPAPER_IDS')
expect(source).toContain('setAllAppNotifications')
expect(source).toContain('appStore.claimApp')
expect(source).toContain('await phone.completeSetup()')
expect(source).toContain(':disabled="setupCompleteBusy"')
expect(source).toContain("phone.t('Setup.ready.saveFailed')")
expect(source).toContain('phone.completeSetup()')
})
it('persists progress and supports resuming or moving backward', () => {
@@ -53,8 +53,6 @@ const passcodeLength = ref<4 | 6>(phone.security.length === 4 ? 4 : 6)
const notificationsEnabled = ref(true)
const notificationSounds = ref(true)
const selectedApps = ref<BuiltinPhoneAppId[]>(['banking', 'garage', 'skyride'])
const setupCompleteBusy = ref(false)
const setupCompleteError = ref('')
const setupApps = (
['banking', 'garage', 'skyride', 'citymarkt', 'picstagram', 'snake'] as const
@@ -206,16 +204,8 @@ function choosePasscodeLength(length: 4 | 6): void {
passcodeResetKey.value += 1
}
async function finish(): Promise<void> {
if (setupCompleteBusy.value) return
setupCompleteBusy.value = true
setupCompleteError.value = ''
const completed = await phone.completeSetup()
setupCompleteBusy.value = false
if (!completed) {
setupCompleteError.value = phone.t('Setup.ready.saveFailed')
return
}
function finish(): void {
phone.completeSetup()
emit('complete')
}
@@ -804,29 +794,12 @@ function skipSetupForDevelopment(): void {
}}</b></span
>
</div>
<SkyButton
class="setup-assistant__primary"
:disabled="setupCompleteBusy"
@click="finish"
>
<SkySpinner
v-if="setupCompleteBusy"
:label="phone.t('Setup.ready.saving')"
:size="18"
/>
<span v-else>{{ phone.t('Setup.ready.enter') }}</span>
</SkyButton>
<p
v-if="setupCompleteError"
class="setup-assistant__error"
role="alert"
>
{{ setupCompleteError }}
</p>
<SkyButton class="setup-assistant__primary" @click="finish">{{
phone.t('Setup.ready.enter')
}}</SkyButton>
<button
type="button"
class="setup-assistant__later"
:disabled="setupCompleteBusy"
@click="moveTo(0)"
>
{{ phone.t('Setup.ready.review') }}
@@ -85,16 +85,4 @@ describe('phone inventory contracts', () => {
)
expect(phoneServer).toContain('preferred_device_imeis[source] = imei')
})
it('keeps non-unique phones bound to one persistent device per character', () => {
const phoneServer = readResourceFile('source/server/phone.lua')
const migration = readResourceFile('source/server/db_migrate.lua')
expect(phoneServer).toContain('if not unique_phones then')
expect(phoneServer).toContain('return map_character_device(source, slot)')
expect(phoneServer).toContain('FROM `sky_phone_character_devices`')
expect(phoneServer).toContain('WHERE `owner_identifier` = ?')
expect(migration).toContain('name = "sky_phone_character_devices"')
expect(migration).toContain('primaryKey = "owner_identifier"')
})
})
@@ -167,66 +167,4 @@ describe('phone device persistence scope', () => {
expect(mockNuiCall).toHaveBeenCalledTimes(2)
expect(phone.deviceRevisions.widgets).toBe(1)
})
it('marks setup complete only after the settings save is acknowledged', async () => {
const completion = deferredResponse<{ revision: number }>()
mockNuiCall.mockReturnValueOnce(completion.promise)
const phone = usePhoneStore()
phone.open({
device: {
data: {
settings: {
payload: {
settings: { setupCompleted: false, setupStep: 9 },
version: 1,
},
revision: 3,
},
},
imei: '111',
name: 'Phone 111',
sim: null,
},
token: 'session-a',
})
const completed = phone.completeSetup()
await Promise.resolve()
expect(phone.preferences.settings.setupCompleted).toBe(false)
completion.resolve({ data: { revision: 4 }, success: true })
await expect(completed).resolves.toBe(true)
expect(phone.preferences.settings.setupCompleted).toBe(true)
expect(phone.deviceRevisions.settings).toBe(4)
})
it('keeps setup open when the completion save is rejected', async () => {
mockNuiCall.mockResolvedValueOnce({
error: 'request_failed',
success: false,
})
const phone = usePhoneStore()
phone.open({
device: {
data: {
settings: {
payload: {
settings: { setupCompleted: false, setupStep: 9 },
version: 1,
},
revision: 3,
},
},
imei: '111',
name: 'Phone 111',
sim: null,
},
token: 'session-a',
})
await expect(phone.completeSetup()).resolves.toBe(false)
expect(phone.preferences.settings.setupCompleted).toBe(false)
expect(phone.deviceRevisions.settings).toBe(3)
})
})
+8 -24
View File
@@ -43,7 +43,7 @@ export type PhoneOpenPayload = {
token?: string
}
const namespaceQueues = new Map<string, Promise<boolean>>()
const namespaceQueues = new Map<string, Promise<void>>()
let nextPersistenceSession = 0
const companiesFallbackLocales = {
@@ -4949,8 +4949,6 @@ const defaultLocales: LocaleTree = {
localOnly: 'Stored on this phone',
enter: 'Enter Sky Phone',
review: 'Review Setup',
saveFailed: 'Setup could not be saved. Try again.',
saving: 'Saving setup',
},
},
Common: {
@@ -5225,13 +5223,13 @@ export const usePhoneStore = defineStore('phone', {
JSON.stringify(device.data.settings?.payload ?? null),
)
},
saveDeviceNamespace(namespace: string, payload: unknown): Promise<boolean> {
saveDeviceNamespace(namespace: string, payload: unknown): void {
const imei = this.device?.imei
if (!imei) {
console.error(
`[Phone persistence] Could not save ${namespace} without an active device.`,
)
return Promise.resolve(false)
return
}
const generation = this.persistenceGeneration
const session = this.persistenceSession
@@ -5245,7 +5243,7 @@ export const usePhoneStore = defineStore('phone', {
this.deviceSessionToken === token
const previous = namespaceQueues.get(queueKey) ?? Promise.resolve()
const queued = previous.then(async () => {
if (!isCurrentScope()) return false
if (!isCurrentScope()) return
const response = await nuiCall<{ revision: number }>('device:save', {
imei,
namespace,
@@ -5260,21 +5258,13 @@ export const usePhoneStore = defineStore('phone', {
Number(response.data?.revision) >= 0
) {
this.deviceRevisions[namespace] = Number(response.data?.revision)
return true
}
if (isCurrentScope()) {
console.error(
`[Phone persistence] Could not save ${namespace}: ${response.error ?? 'request_failed'}`,
)
}
return false
})
const tracked = queued.finally(() => {
if (namespaceQueues.get(queueKey) === tracked)
namespaceQueues.delete(queueKey)
})
namespaceQueues.set(queueKey, tracked)
return tracked
},
async flushDevicePersistence(): Promise<void> {
const imei = this.device?.imei
@@ -5339,16 +5329,10 @@ export const usePhoneStore = defineStore('phone', {
)
this.saveDeviceNamespace('settings', this.preferences)
},
async completeSetup(): Promise<boolean> {
const completedPreferences = cloneJsonData(this.preferences)
completedPreferences.settings.setupCompleted = true
completedPreferences.settings.setupStep = PHONE_SETUP_LAST_STEP
const saved = await this.saveDeviceNamespace(
'settings',
completedPreferences,
)
if (saved) this.preferences = completedPreferences
return saved
completeSetup(): void {
this.preferences.settings.setupCompleted = true
this.preferences.settings.setupStep = PHONE_SETUP_LAST_STEP
this.saveDeviceNamespace('settings', this.preferences)
},
resetAfterFactoryReset(): void {
this.persistenceGeneration += 1
-1
View File
@@ -123,7 +123,6 @@ Locales["de"] = {
ready = {
eyebrow = "Einrichtung abgeschlossen", title = "Willkommen, {name}", body = "Dein Sky Phone ist eingerichtet und bereit. Du kannst alles später in den Einstellungen anpassen.",
localOnly = "Auf diesem Handy gespeichert", enter = "Sky Phone öffnen", review = "Einrichtung prüfen",
saving = "Einrichtung wird gespeichert", saveFailed = "Die Einrichtung konnte nicht gespeichert werden. Versuche es erneut.",
},
},
Common = {
-1
View File
@@ -123,7 +123,6 @@ Locales["en"] = {
ready = {
eyebrow = "Setup Complete", title = "Welcome, {name}", body = "Your Sky Phone is configured and ready. Your choices can always be refined in Settings.",
localOnly = "Stored on this phone", enter = "Enter Sky Phone", review = "Review Setup",
saving = "Saving setup", saveFailed = "Setup could not be saved. Try again.",
},
},
Common = {