Merge branch 'stack/agent-profiles-7-docs' into stack/agent-profiles-8-authoring
# Conflicts: # packages/client/ui-agent-preset/src/client/settings-store.ts # packages/client/ui-agent-preset/tests/settings-store.spec.ts # packages/host/apiproxy/src/api-proxy.ts # packages/host/apiproxy/tests/api-proxy-agent-preset.spec.ts
This commit is contained in:
@@ -65,6 +65,12 @@ export interface AgentPresetOption {
|
||||
export interface AgentPresetSettingsState {
|
||||
status: 'idle' | 'loading' | 'ready' | 'saving' | 'unavailable' | 'error'
|
||||
error: string | null
|
||||
/**
|
||||
* Whether this browser may persist the choice at all. `settings.describe` is
|
||||
* loopback-only and reports a read-only provider as `writable: false`; the
|
||||
* row then shows the current default and disables the control rather than
|
||||
* offering a write the gateway will refuse.
|
||||
*/
|
||||
writable: boolean
|
||||
currentValue: string
|
||||
options: readonly AgentPresetOption[]
|
||||
@@ -73,6 +79,8 @@ export interface AgentPresetSettingsState {
|
||||
const INITIAL: AgentPresetSettingsState = {
|
||||
status: 'idle',
|
||||
error: null,
|
||||
// Assumed until `load()` asks; a row that has not read yet renders nothing
|
||||
// interactive anyway (status 'idle').
|
||||
writable: true,
|
||||
currentValue: '',
|
||||
options: [],
|
||||
@@ -110,9 +118,15 @@ export class AgentPresetSettingsController {
|
||||
this.set({ status: 'unavailable', options: [], currentValue: '' })
|
||||
return
|
||||
}
|
||||
// The roster says what may be chosen; `settings.describe` says whether
|
||||
// this browser may write the choice down. A non-loopback browser reaches
|
||||
// neither method, so a refused describe leaves the row read-only rather
|
||||
// than offering a control whose write answers `settings-not-exposed`.
|
||||
const described = await this.api.settings.describe({})
|
||||
this.set({
|
||||
status: 'ready',
|
||||
error: null,
|
||||
writable: described.result.ok && described.result.value.writable,
|
||||
options: presets.map(preset => ({
|
||||
id: preset.id,
|
||||
trust: preset.trust,
|
||||
|
||||
@@ -92,6 +92,11 @@ async function bench() {
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
// The row reads this to learn whether this browser may write at all.
|
||||
describe: () => Promise.resolve({
|
||||
rpcId: 'r',
|
||||
result: { ok: true as const, value: { writable: true, hasDocument: true, namespaces: [] } },
|
||||
}),
|
||||
update: (payload: { patch: unknown }) => { calls.push(`settings:${JSON.stringify(payload.patch)}`); return Promise.resolve({ rpcId: 'r', result: { ok: true as const, value: {} } }) },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -18,7 +18,13 @@ interface Recorded { ns: string; patch: unknown }
|
||||
/** A client whose roster and write outcome the test controls. */
|
||||
function fakeApi(
|
||||
presets: { id: string; trust: 'system' | 'user'; isDefault: boolean }[],
|
||||
options: { writes?: Recorded[]; failWrite?: string; failList?: string; failWriteWith?: Error } = {},
|
||||
options: {
|
||||
writes?: Recorded[]
|
||||
failWrite?: string
|
||||
failList?: string
|
||||
failWriteWith?: Error
|
||||
readOnly?: boolean
|
||||
} = {},
|
||||
): IApiClient {
|
||||
return {
|
||||
agentPresets: {
|
||||
@@ -27,6 +33,15 @@ function fakeApi(
|
||||
: { rpcId: 'r', result: { ok: false as const, error: { code: 'internal', message: options.failList, details: {} } } }),
|
||||
},
|
||||
settings: {
|
||||
// Loopback-only in production; a read-only provider answers writable:false
|
||||
// and the row disables its control instead of offering a refused write.
|
||||
describe: () => Promise.resolve({
|
||||
rpcId: 'r',
|
||||
result: {
|
||||
ok: true as const,
|
||||
value: { writable: options.readOnly !== true, hasDocument: true, namespaces: [] },
|
||||
},
|
||||
}),
|
||||
update: (payload: { ns: string; patch: unknown }) => {
|
||||
options.writes?.push({ ns: payload.ns, patch: payload.patch })
|
||||
if (options.failWriteWith !== undefined) return Promise.reject(options.failWriteWith)
|
||||
@@ -44,6 +59,20 @@ function fakeApi(
|
||||
}
|
||||
|
||||
describe('the agent-preset settings controller', () => {
|
||||
it('disables the control when this browser may not write settings', async () => {
|
||||
const controller = new AgentPresetSettingsController(fakeApi([
|
||||
{ id: 'standard', trust: 'system', isDefault: true },
|
||||
], { readOnly: true }))
|
||||
|
||||
await controller.load()
|
||||
|
||||
// `settings.describe` is loopback-only and reports a read-only provider;
|
||||
// offering a control whose write answers `settings-not-exposed` would
|
||||
// promise a switch the host refuses.
|
||||
expect(controller.store.getSnapshot().writable).toBe(false)
|
||||
expect(controller.store.getSnapshot().currentValue).toBe('standard')
|
||||
})
|
||||
|
||||
it('derives options and the current default from one roster call', async () => {
|
||||
const controller = new AgentPresetSettingsController(fakeApi([
|
||||
{ id: 'standard', trust: 'system', isDefault: true },
|
||||
|
||||
Reference in New Issue
Block a user