From a42d51f8cf2aae6896a367d8b754140a732497fb Mon Sep 17 00:00:00 2001 From: David Crespo Date: Sat, 24 Aug 2024 15:47:32 -0500 Subject: [PATCH] error if parent selectors given when resources are fetched by ID --- mock-api/msw/db.ts | 76 +++++++++++++++++++++++++++++++--------- mock-api/msw/handlers.ts | 26 ++++++++------ 2 files changed, 75 insertions(+), 27 deletions(-) diff --git a/mock-api/msw/db.ts b/mock-api/msw/db.ts index 4458dc4c1..09ea99a75 100644 --- a/mock-api/msw/db.ts +++ b/mock-api/msw/db.ts @@ -26,17 +26,25 @@ export const notFoundErr = (msg: string) => { return json({ error_code: 'ObjectNotFound', message } as const, { status: 404 }) } -export const badSelectorErr = (resource: string, parents: string[]) => { - const message = `when ${resource} is specified by ID, ${commaSeries(parents, 'and')} should not be specified` - return json({ error_code: 'InvalidRequest', message }, { status: 400 }) -} - export const lookupById = (table: T[], id: string) => { const item = table.find((i) => i.id === id) if (!item) throw notFoundErr(`by id ${id}`) return item } +function ensureNoParentSelector( + resource: string, + selector: Record +) { + const keysWithValues = Object.entries(selector) + .filter(([_, v]) => v) + .map(([k]) => k) + if (keysWithValues.length > 0) { + const message = `when ${resource} is specified by ID, ${commaSeries(keysWithValues, 'and')} should not be specified` + throw json({ error_code: 'InvalidRequest', message }, { status: 400 }) + } +} + export const getIpFromPool = (poolName: string | undefined) => { const pool = lookup.ipPool({ pool: poolName }) const ipPoolRange = db.ipPoolRanges.find((range) => range.ip_pool_id === pool.id) @@ -63,7 +71,10 @@ export const lookup = { instance({ instance: id, ...projectSelector }: PP.Instance): Json { if (!id) throw notFoundErr('no instance specified') - if (isUuid(id)) return lookupById(db.instances, id) + if (isUuid(id)) { + ensureNoParentSelector('instance', projectSelector) + return lookupById(db.instances, id) + } const project = lookup.project(projectSelector) const instance = db.instances.find((i) => i.project_id === project.id && i.name === id) @@ -77,7 +88,10 @@ export const lookup = { }: PP.NetworkInterface): Json { if (!id) throw notFoundErr('no NIC specified') - if (isUuid(id)) return lookupById(db.networkInterfaces, id) + if (isUuid(id)) { + ensureNoParentSelector('network interface', instanceSelector) + return lookupById(db.networkInterfaces, id) + } const instance = lookup.instance(instanceSelector) @@ -91,7 +105,10 @@ export const lookup = { disk({ disk: id, ...projectSelector }: PP.Disk): Json { if (!id) throw notFoundErr('no disk specified') - if (isUuid(id)) return lookupById(db.disks, id) + if (isUuid(id)) { + ensureNoParentSelector('disk', projectSelector) + return lookupById(db.disks, id) + } const project = lookup.project(projectSelector) @@ -104,7 +121,7 @@ export const lookup = { if (!id) throw notFoundErr('no floating IP specified') if (isUuid(id)) { - if (projectSelector.project) throw badSelectorErr('floating IP', ['project']) + ensureNoParentSelector('floating IP', projectSelector) return lookupById(db.floatingIps, id) } @@ -119,7 +136,10 @@ export const lookup = { snapshot({ snapshot: id, ...projectSelector }: PP.Snapshot): Json { if (!id) throw notFoundErr('no snapshot specified') - if (isUuid(id)) return lookupById(db.snapshots, id) + if (isUuid(id)) { + ensureNoParentSelector('snapshot', projectSelector) + return lookupById(db.snapshots, id) + } const project = lookup.project(projectSelector) const snapshot = db.snapshots.find((i) => i.project_id === project.id && i.name === id) @@ -130,7 +150,10 @@ export const lookup = { vpc({ vpc: id, ...projectSelector }: PP.Vpc): Json { if (!id) throw notFoundErr('no VPC specified') - if (isUuid(id)) return lookupById(db.vpcs, id) + if (isUuid(id)) { + ensureNoParentSelector('vpc', projectSelector) + return lookupById(db.vpcs, id) + } const project = lookup.project(projectSelector) const vpc = db.vpcs.find((v) => v.project_id === project.id && v.name === id) @@ -141,7 +164,10 @@ export const lookup = { vpcRouter({ router: id, ...vpcSelector }: PP.VpcRouter): Json { if (!id) throw notFoundErr('no router specified') - if (isUuid(id)) return lookupById(db.vpcRouters, id) + if (isUuid(id)) { + ensureNoParentSelector('router', vpcSelector) + return lookupById(db.vpcRouters, id) + } const vpc = lookup.vpc(vpcSelector) const router = db.vpcRouters.find((r) => r.vpc_id === vpc.id && r.name === id) @@ -155,7 +181,10 @@ export const lookup = { }: PP.VpcRouterRoute): Json { if (!id) throw notFoundErr('no route specified') - if (isUuid(id)) return lookupById(db.vpcRouterRoutes, id) + if (isUuid(id)) { + ensureNoParentSelector('route', routerSelector) + return lookupById(db.vpcRouterRoutes, id) + } const router = lookup.vpcRouter(routerSelector) const route = db.vpcRouterRoutes.find( @@ -168,7 +197,10 @@ export const lookup = { vpcSubnet({ subnet: id, ...vpcSelector }: PP.VpcSubnet): Json { if (!id) throw notFoundErr('no subnet specified') - if (isUuid(id)) return lookupById(db.vpcSubnets, id) + if (isUuid(id)) { + ensureNoParentSelector('subnet', vpcSelector) + return lookupById(db.vpcSubnets, id) + } const vpc = lookup.vpc(vpcSelector) const subnet = db.vpcSubnets.find((s) => s.vpc_id === vpc.id && s.name === id) @@ -179,8 +211,13 @@ export const lookup = { image({ image: id, project: projectId }: PP.Image): Json { if (!id) throw notFoundErr('no image specified') - if (isUuid(id)) return lookupById(db.images, id) + if (isUuid(id)) { + ensureNoParentSelector('image', { project: projectId }) + return lookupById(db.images, id) + } + // TODO: is this logic right? seems kinda weird. you should be able to look + // up either kind by ID. we should probably have two lookup functions let image: Json | undefined if (projectId === undefined) { // silo image @@ -264,8 +301,15 @@ export const lookup = { samlIdp({ provider: id, silo }: PP.IdentityProvider): Json { if (!id) throw notFoundErr('no IdP specified') - const dbSilo = lookup.silo({ silo }) + if (isUuid(id)) { + ensureNoParentSelector('identity provider', { silo }) + return lookupById( + db.identityProviders.filter((o) => o.type === 'saml').map((o) => o.provider), + id + ) + } + const dbSilo = lookup.silo({ silo }) const dbIdp = db.identityProviders.find( ({ type, siloId, provider }) => type === 'saml' && siloId === dbSilo.id && provider.name === id diff --git a/mock-api/msw/handlers.ts b/mock-api/msw/handlers.ts index 29dff60b8..b58f96982 100644 --- a/mock-api/msw/handlers.ts +++ b/mock-api/msw/handlers.ts @@ -288,15 +288,17 @@ export const handlers = makeHandlers({ return 204 }, - floatingIpAttach({ path, query, body }) { - const floatingIp = lookup.floatingIp({ ...path, ...query }) - if (floatingIp.instance_id) { + floatingIpAttach({ path: { floatingIp }, query: { project }, body }) { + const dbFloatingIp = lookup.floatingIp({ floatingIp, project }) + if (dbFloatingIp.instance_id) { throw 'floating IP cannot be attached to one instance while still attached to another' } - const instance = lookup.instance({ ...path, ...query, instance: body.parent }) - floatingIp.instance_id = instance.id + // TODO: make sure the logic around when project is passed matches + // the API + const dbInstance = lookup.instance({ instance: body.parent }) + dbFloatingIp.instance_id = dbInstance.id - return floatingIp + return dbFloatingIp }, floatingIpDetach({ path, query }) { const floatingIp = lookup.floatingIp({ ...path, ...query }) @@ -359,13 +361,15 @@ export const handlers = makeHandlers({ return json(image, { status: 202 }) }, - imageDemote({ path, query }) { - const image = lookup.image({ ...path, ...query }) - const project = lookup.project({ ...path, ...query }) + imageDemote({ path: { image }, query: { project } }) { + // TODO: change this to lookup.siloImage when I add that. or at least + // check API logic to make sure we match it + const dbImage = lookup.image({ image }) + const dbProject = lookup.project({ project }) - image.project_id = project.id + dbImage.project_id = dbProject.id - return json(image, { status: 202 }) + return json(dbImage, { status: 202 }) }, instanceList({ query }) { const project = lookup.project(query)