226 lines
6.7 KiB
TypeScript
226 lines
6.7 KiB
TypeScript
import { describe, test, expect } from 'bun:test'
|
|
import {
|
|
getAutomationStateSnapshot,
|
|
getAutomationStateEventPayload,
|
|
automationStatesEqual,
|
|
} from '../services/automationState'
|
|
import type { AutomationStateResponse } from '../types/api'
|
|
|
|
// =============================================================================
|
|
// normalizeAutomationState (via getAutomationStateSnapshot)
|
|
// =============================================================================
|
|
|
|
describe('normalizeAutomationState', () => {
|
|
test('returns undefined when metadata has no automation_state key', () => {
|
|
expect(getAutomationStateSnapshot({})).toBeUndefined()
|
|
expect(getAutomationStateSnapshot({ other: true })).toBeUndefined()
|
|
expect(getAutomationStateSnapshot(null)).toBeUndefined()
|
|
expect(getAutomationStateSnapshot(undefined)).toBeUndefined()
|
|
})
|
|
|
|
test('returns disabled state for null automation_state', () => {
|
|
const result = getAutomationStateSnapshot({ automation_state: null })
|
|
expect(result).toEqual({
|
|
enabled: false,
|
|
phase: null,
|
|
next_tick_at: null,
|
|
sleep_until: null,
|
|
})
|
|
})
|
|
|
|
test('returns disabled state for non-object automation_state', () => {
|
|
for (const val of ['string', 123, true, []]) {
|
|
const result = getAutomationStateSnapshot({ automation_state: val })
|
|
expect(result?.enabled).toBe(false)
|
|
}
|
|
})
|
|
|
|
test('normalizes enabled: true correctly', () => {
|
|
const result = getAutomationStateSnapshot({
|
|
automation_state: { enabled: true },
|
|
})
|
|
expect(result?.enabled).toBe(true)
|
|
})
|
|
|
|
test('normalizes enabled to false for non-true values', () => {
|
|
const result = getAutomationStateSnapshot({
|
|
automation_state: { enabled: 'yes' },
|
|
})
|
|
expect(result?.enabled).toBe(false)
|
|
})
|
|
|
|
test('accepts phase: standby', () => {
|
|
const result = getAutomationStateSnapshot({
|
|
automation_state: { enabled: true, phase: 'standby' },
|
|
})
|
|
expect(result?.phase).toBe('standby')
|
|
})
|
|
|
|
test('accepts phase: sleeping', () => {
|
|
const result = getAutomationStateSnapshot({
|
|
automation_state: { enabled: true, phase: 'sleeping' },
|
|
})
|
|
expect(result?.phase).toBe('sleeping')
|
|
})
|
|
|
|
test('rejects invalid phase values', () => {
|
|
for (const phase of ['running', 'idle', 'active', '', null]) {
|
|
const result = getAutomationStateSnapshot({
|
|
automation_state: { enabled: true, phase },
|
|
})
|
|
expect(result?.phase).toBeNull()
|
|
}
|
|
})
|
|
|
|
test('normalizes next_tick_at as number', () => {
|
|
const result = getAutomationStateSnapshot({
|
|
automation_state: { enabled: true, next_tick_at: 12345 },
|
|
})
|
|
expect(result?.next_tick_at).toBe(12345)
|
|
})
|
|
|
|
test('normalizes next_tick_at as null for non-number', () => {
|
|
const result = getAutomationStateSnapshot({
|
|
automation_state: { enabled: true, next_tick_at: 'soon' },
|
|
})
|
|
expect(result?.next_tick_at).toBeNull()
|
|
})
|
|
|
|
test('normalizes sleep_until as number', () => {
|
|
const result = getAutomationStateSnapshot({
|
|
automation_state: { enabled: true, sleep_until: 99999 },
|
|
})
|
|
expect(result?.sleep_until).toBe(99999)
|
|
})
|
|
|
|
test('normalizes sleep_until as null for non-number', () => {
|
|
const result = getAutomationStateSnapshot({
|
|
automation_state: { enabled: true, sleep_until: false },
|
|
})
|
|
expect(result?.sleep_until).toBeNull()
|
|
})
|
|
|
|
test('fully normalizes a complete valid state', () => {
|
|
const result = getAutomationStateSnapshot({
|
|
automation_state: {
|
|
enabled: true,
|
|
phase: 'sleeping',
|
|
next_tick_at: 100,
|
|
sleep_until: 200,
|
|
},
|
|
})
|
|
expect(result).toEqual({
|
|
enabled: true,
|
|
phase: 'sleeping',
|
|
next_tick_at: 100,
|
|
sleep_until: 200,
|
|
})
|
|
})
|
|
})
|
|
|
|
// =============================================================================
|
|
// getAutomationStateEventPayload
|
|
// =============================================================================
|
|
|
|
describe('getAutomationStateEventPayload', () => {
|
|
test('returns disabled default when no automation_state in metadata', () => {
|
|
const result = getAutomationStateEventPayload({})
|
|
expect(result).toEqual({
|
|
enabled: false,
|
|
phase: null,
|
|
next_tick_at: null,
|
|
sleep_until: null,
|
|
})
|
|
})
|
|
|
|
test('returns disabled default for null metadata', () => {
|
|
const result = getAutomationStateEventPayload(null)
|
|
expect(result).toEqual({
|
|
enabled: false,
|
|
phase: null,
|
|
next_tick_at: null,
|
|
sleep_until: null,
|
|
})
|
|
})
|
|
|
|
test('returns normalized state when automation_state present', () => {
|
|
const result = getAutomationStateEventPayload({
|
|
automation_state: {
|
|
enabled: true,
|
|
phase: 'standby',
|
|
next_tick_at: 50,
|
|
sleep_until: 60,
|
|
},
|
|
})
|
|
expect(result).toEqual({
|
|
enabled: true,
|
|
phase: 'standby',
|
|
next_tick_at: 50,
|
|
sleep_until: 60,
|
|
})
|
|
})
|
|
|
|
test('returns a new object each call (not frozen reference)', () => {
|
|
const a = getAutomationStateEventPayload({})
|
|
const b = getAutomationStateEventPayload({})
|
|
expect(a).toEqual(b)
|
|
expect(a).not.toBe(b)
|
|
})
|
|
})
|
|
|
|
// =============================================================================
|
|
// automationStatesEqual
|
|
// =============================================================================
|
|
|
|
describe('automationStatesEqual', () => {
|
|
const base: AutomationStateResponse = {
|
|
enabled: true,
|
|
phase: 'standby',
|
|
next_tick_at: 100,
|
|
sleep_until: 200,
|
|
}
|
|
|
|
test('returns true for identical states', () => {
|
|
expect(automationStatesEqual(base, { ...base })).toBe(true)
|
|
})
|
|
|
|
test('returns false when enabled differs', () => {
|
|
expect(automationStatesEqual(base, { ...base, enabled: false })).toBe(false)
|
|
})
|
|
|
|
test('returns false when phase differs', () => {
|
|
expect(automationStatesEqual(base, { ...base, phase: 'sleeping' })).toBe(
|
|
false,
|
|
)
|
|
expect(automationStatesEqual(base, { ...base, phase: null })).toBe(false)
|
|
})
|
|
|
|
test('returns false when next_tick_at differs', () => {
|
|
expect(automationStatesEqual(base, { ...base, next_tick_at: 999 })).toBe(
|
|
false,
|
|
)
|
|
expect(automationStatesEqual(base, { ...base, next_tick_at: null })).toBe(
|
|
false,
|
|
)
|
|
})
|
|
|
|
test('returns false when sleep_until differs', () => {
|
|
expect(automationStatesEqual(base, { ...base, sleep_until: 999 })).toBe(
|
|
false,
|
|
)
|
|
expect(automationStatesEqual(base, { ...base, sleep_until: null })).toBe(
|
|
false,
|
|
)
|
|
})
|
|
|
|
test('returns true when both are disabled defaults', () => {
|
|
const disabled: AutomationStateResponse = {
|
|
enabled: false,
|
|
phase: null,
|
|
next_tick_at: null,
|
|
sleep_until: null,
|
|
}
|
|
expect(automationStatesEqual(disabled, { ...disabled })).toBe(true)
|
|
})
|
|
})
|