Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion app/api/util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
import { describe, expect, it, test } from 'vitest'

import { genName, parsePortRange, synthesizeData } from './util'
import { diskCan, genName, instanceCan, parsePortRange, synthesizeData } from './util'

describe('parsePortRange', () => {
describe('parses', () => {
Expand Down Expand Up @@ -136,3 +136,22 @@ describe('synthesizeData', () => {
])
})
})

test('instanceCan', () => {
expect(instanceCan.start({ runState: 'running' })).toBe(false)
expect(instanceCan.start({ runState: 'stopped' })).toBe(true)

// @ts-expect-error typechecker rejects actions that don't exist
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
instanceCan.abc
})

test('diskCan', () => {
expect(diskCan.delete({ state: { state: 'creating' } })).toBe(false)
expect(diskCan.delete({ state: { state: 'attached', instance: 'xyz' } })).toBe(false)
expect(diskCan.delete({ state: { state: 'detached' } })).toBe(true)

// @ts-expect-error typechecker rejects actions that don't exist
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
diskCan.abc
})

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed the @ts-expect-errors fail on main:

Screenshot 2024-10-15 at 2 03 47 PM

12 changes: 6 additions & 6 deletions app/api/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const genName = (...parts: [string, ...string[]]) => {
)
}

const instanceActions: Record<string, InstanceState[]> = {
const instanceActions = {
// NoVmm maps to to Stopped:
// https://github.com/oxidecomputer/omicron/blob/6dd9802/nexus/db-model/src/instance_state.rs#L55

Expand Down Expand Up @@ -120,12 +120,12 @@ const instanceActions: Record<string, InstanceState[]> = {
updateNic: ['stopped'],
// https://github.com/oxidecomputer/omicron/blob/6dd9802/nexus/src/app/instance.rs#L1520-L1522
serialConsole: ['running', 'rebooting', 'migrating', 'repairing'],
}
} satisfies Record<string, InstanceState[]>

// setting .states is a cute way to make it ergonomic to call the test function
// while also making the states available directly

export const instanceCan = R.mapValues(instanceActions, (states) => {
export const instanceCan = R.mapValues(instanceActions, (states: InstanceState[]) => {
const test = (i: { runState: InstanceState }) => states.includes(i.runState)
test.states = states
return test
Expand All @@ -140,7 +140,7 @@ export function instanceTransitioning({ runState }: Instance) {
)
}

const diskActions: Record<string, DiskState['state'][]> = {
const diskActions = {
// this is a weird one because the list of states is dynamic and it includes
// 'creating' in the unwind of the disk create saga, but does not include
// 'creating' in the disk delete saga, which is what we care about
Expand All @@ -154,9 +154,9 @@ const diskActions: Record<string, DiskState['state'][]> = {
detach: ['attached'],
// https://github.com/oxidecomputer/omicron/blob/3093818/nexus/db-queries/src/db/datastore/instance.rs#L1077-L1081
setAsBootDisk: ['attached'],
}
} satisfies Record<string, DiskState['state'][]>

export const diskCan = R.mapValues(diskActions, (states) => {
export const diskCan = R.mapValues(diskActions, (states: DiskState['state'][]) => {
// only have to Pick because we want this to work for both Disk and
// Json<Disk>, which we pass to it in the MSW handlers
const test = (d: Pick<Disk, 'state'>) => states.includes(d.state.state)
Expand Down