diff --git a/tests/api-tests/access-control/authed.test.ts b/tests/api-tests/access-control/authed.test.ts index 659dc63fe2a..814b0301948 100644 --- a/tests/api-tests/access-control/authed.test.ts +++ b/tests/api-tests/access-control/authed.test.ts @@ -87,398 +87,415 @@ describe('Authed', () => { await testEnv.disconnect(); }); - describe('create', () => { - (['imperative'] as const).forEach(mode => { - describe(mode, () => { - listAccessVariations - .filter(({ create }) => create) - .forEach(access => { - test(`allowed: ${JSON.stringify(access)}`, async () => { - const listKey = nameFn[mode](access); - const data = { name: 'bar' }; - const item = await context.lists[listKey].createOne({ data }); - expect(item).not.toBe(null); - expect(item.id).not.toBe(null); - await context.sudo().lists[listKey].deleteOne({ where: { id: item.id } }); - }); - }); - }); - }); - (['static'] as const).forEach(mode => { - describe(mode, () => { - fieldMatrix - .filter(({ create }) => create) - .forEach(access => { - test(`field allowed: ${JSON.stringify(access)}`, async () => { - const listAccess = { create: true, read: true, update: true, delete: true }; - const listKey = nameFn[mode](listAccess); - const fieldName = getFieldName(access); - const item = await context.lists[listKey].createOne({ - data: { [fieldName]: 'bar' }, - query: `id ${access.read ? fieldName : ''}`, - }); - expect(item).not.toBe(null); - expect(item.id).not.toBe(null); - if (access.read) { - expect(item[fieldName]).toBe('bar'); - } else { - expect(item[fieldName]).toBe(undefined); - } - await context.sudo().lists[listKey].deleteOne({ where: { id: item.id } }); - }); - }); - }); - }); - (['imperative'] as const).forEach(mode => { - describe(mode, () => { - fieldMatrix - .filter(({ create }) => create) - .forEach(access => { - test(`field allowed: ${JSON.stringify(access)}`, async () => { - const listAccess = { create: true, read: true, update: true, delete: true }; - const listKey = nameFn[mode](listAccess); - const fieldName = getFieldName(access); - const item = await context.lists[listKey].createOne({ - data: { [fieldName]: 'bar' }, + describe('List access', () => { + describe('create', () => { + (['imperative'] as const).forEach(mode => { + describe(mode, () => { + listAccessVariations + .filter(({ create }) => create) + .forEach(access => { + test(`allowed: ${JSON.stringify(access)}`, async () => { + const listKey = nameFn[mode](access); + const data = { name: 'bar' }; + const item = await context.lists[listKey].createOne({ data }); + expect(item).not.toBe(null); + expect(item.id).not.toBe(null); + await context.sudo().lists[listKey].deleteOne({ where: { id: item.id } }); }); - expect(item).not.toBe(null); - expect(item.id).not.toBe(null); - await context.sudo().lists[listKey].deleteOne({ where: { id: item.id } }); }); - }); + }); }); }); - }); - describe('read', () => { - test('authed user', async () => { - const query = `query { authenticatedItem { ... on User { id yesRead noRead } } }`; - const _context = await context.withSession({ itemId: user.id, listKey: 'User', data: user }); - const { data, errors } = await _context.graphql.raw({ query }); - expect(data).toEqual({ - authenticatedItem: { id: user.id, yesRead: user.yesRead, noRead: null }, + describe('read', () => { + test('authed user', async () => { + const query = `query { authenticatedItem { ... on User { id yesRead noRead } } }`; + const _context = await context.withSession({ + itemId: user.id, + listKey: 'User', + data: user, + }); + const { data, errors } = await _context.graphql.raw({ query }); + expect(data).toEqual({ + authenticatedItem: { id: user.id, yesRead: user.yesRead, noRead: null }, + }); + expectAccessDenied('dev', false, undefined, errors, [ + { path: ['authenticatedItem', 'noRead'] }, + ]); }); - expectAccessDenied('dev', false, undefined, errors, [ - { path: ['authenticatedItem', 'noRead'] }, - ]); - }); - (['imperative', 'declarative'] as const).forEach(mode => { - describe(mode, () => { - listAccessVariations - .filter(({ read }) => read) - .forEach(access => { - const listKey = nameFn[mode](access); + (['imperative', 'declarative'] as const).forEach(mode => { + describe(mode, () => { + listAccessVariations + .filter(({ read }) => read) + .forEach(access => { + const listKey = nameFn[mode](access); - test(`'all' allowed: ${JSON.stringify(access)}`, async () => { - const _items = await context.lists[listKey].findMany(); - if (mode === 'imperative') { - expect(_items).toHaveLength(2); - } else { - expect(_items).toHaveLength(1); // We can only read the ones our permission filter allow - } - }); + test(`'all' allowed: ${JSON.stringify(access)}`, async () => { + const _items = await context.lists[listKey].findMany(); + if (mode === 'imperative') { + expect(_items).toHaveLength(2); + } else { + expect(_items).toHaveLength(1); // We can only read the ones our permission filter allow + } + }); - test(`meta allowed: ${JSON.stringify(access)}`, async () => { - const count = await context.lists[listKey].count(); - if (mode === 'imperative') { - expect(count).toEqual(2); - } else { - expect(count).toEqual(1); // We can only read the ones our permission filter allow - } - }); + test(`meta allowed: ${JSON.stringify(access)}`, async () => { + const count = await context.lists[listKey].count(); + if (mode === 'imperative') { + expect(count).toEqual(2); + } else { + expect(count).toEqual(1); // We can only read the ones our permission filter allow + } + }); - test(`single allowed: ${JSON.stringify(access)}`, async () => { - const validId = items[listKey].find(({ name }) => name === 'Hello')?.id; - const item = await context.lists[listKey].findOne({ where: { id: validId } }); - expect(item).not.toBe(null); - expect(item.id).toEqual(validId); - }); + test(`single allowed: ${JSON.stringify(access)}`, async () => { + const validId = items[listKey].find(({ name }) => name === 'Hello')?.id; + const item = await context.lists[listKey].findOne({ where: { id: validId } }); + expect(item).not.toBe(null); + expect(item.id).toEqual(validId); + }); - test(`single not allowed: ${JSON.stringify(access)}`, async () => { - const { itemQueryName } = context.gqlNames(listKey); - const invalidId = items[listKey].find(({ name }) => name !== 'Hello')?.id; - const query = `query { ${itemQueryName}(where: { id: "${invalidId}" }) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - if (mode === 'imperative') { - // Imperative should work - expect(errors).toBe(undefined); - expect(data?.[itemQueryName]).not.toBe(null); - expect(data?.[itemQueryName].id).toEqual(invalidId); - } else { - // but declarative should not - expectNoAccess(data, errors, itemQueryName); - } - }); + test(`single not allowed: ${JSON.stringify(access)}`, async () => { + const { itemQueryName } = context.gqlNames(listKey); + const invalidId = items[listKey].find(({ name }) => name !== 'Hello')?.id; + const query = `query { ${itemQueryName}(where: { id: "${invalidId}" }) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + if (mode === 'imperative') { + // Imperative should work + expect(errors).toBe(undefined); + expect(data?.[itemQueryName]).not.toBe(null); + expect(data?.[itemQueryName].id).toEqual(invalidId); + } else { + // but declarative should not + expectNoAccess(data, errors, itemQueryName); + } + }); - test(`single not existing: ${JSON.stringify(access)}`, async () => { - const { itemQueryName } = context.gqlNames(listKey); - const query = `query { ${itemQueryName}(where: { id: "${FAKE_ID[provider]}" }) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectNoAccess(data, errors, itemQueryName); - }); + test(`single not existing: ${JSON.stringify(access)}`, async () => { + const { itemQueryName } = context.gqlNames(listKey); + const query = `query { ${itemQueryName}(where: { id: "${FAKE_ID[provider]}" }) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + expectNoAccess(data, errors, itemQueryName); + }); - test(`multiple not existing: ${JSON.stringify(access)}`, async () => { - const _items = await context.lists[listKey].findMany({ - where: { id: { in: [FAKE_ID[provider], FAKE_ID_2[provider]] } }, + test(`multiple not existing: ${JSON.stringify(access)}`, async () => { + const _items = await context.lists[listKey].findMany({ + where: { id: { in: [FAKE_ID[provider], FAKE_ID_2[provider]] } }, + }); + expect(_items).toHaveLength(0); }); - expect(_items).toHaveLength(0); }); - }); + }); }); }); - (['imperative', 'static'] as const).forEach(mode => { - describe(mode, () => { - fieldMatrix - .filter(({ read }) => read) - .forEach(access => { - test(`field allowed - singular: ${JSON.stringify(access)}`, async () => { - const listAccess = { - create: true, - read: true, - update: true, - delete: true, - }; - const listKey = nameFn[mode](listAccess); - const item = items[listKey][0]; - const fieldName = getFieldName(access); - await context.sudo().lists[listKey].updateOne({ - where: { id: item.id }, - data: { [fieldName]: 'hello' }, - }); - const _item = await context.lists[listKey].findOne({ - where: { id: item.id }, - query: `id ${fieldName}`, - }); - expect(_item).not.toBe(null); - expect(_item.id).not.toBe(null); - expect(_item[fieldName]).toBe('hello'); - }); - test(`field allowed - multi: ${JSON.stringify(access)}`, async () => { - const listAccess = { create: true, read: true, update: true, delete: true }; - const listKey = nameFn[mode](listAccess); - const item = items[listKey][0]; - const fieldName = getFieldName(access); - await context.sudo().lists[listKey].updateOne({ - where: { id: item.id }, - data: { [fieldName]: 'hello' }, + + describe('update', () => { + (['imperative', 'declarative'] as const).forEach(mode => { + describe(mode, () => { + listAccessVariations + .filter(({ update }) => update) + .forEach(access => { + test(`denies missing: ${JSON.stringify(access)}`, async () => { + const updateMutationName = `update${nameFn[mode](access)}`; + const query = `mutation { ${updateMutationName}(where: { id: "${FAKE_ID[provider]}" }, data: { name: "bar" }) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + expectNoAccess(data, errors, updateMutationName); }); - const _items = await context.lists[listKey].findMany({ query: `id ${fieldName}` }); - expect(_items).not.toBe(null); - expect(_items).toHaveLength(2); - for (const _item of _items) { - expect(_item.id).not.toBe(null); - if (_item.id === item.id) { - expect(_item[fieldName]).toEqual('hello'); + + test(`denies by declarative: ${JSON.stringify(access)}`, async () => { + const updateMutationName = `update${nameFn[mode](access)}`; + const singleQueryName = nameFn[mode](access); + const invalidId = items[singleQueryName].find(({ name }) => name !== 'Hello')?.id; + const query = `mutation { ${updateMutationName}(where: { id: "${invalidId}" }, data: { name: "bar" }) { id name } }`; + const { data, errors } = await context.graphql.raw({ query }); + if (mode === 'imperative') { + expect(errors).toBe(undefined); + expect(data?.[updateMutationName]).not.toBe(null); + expect(data?.[updateMutationName].id).toEqual(invalidId); + expect(data?.[updateMutationName].name).toEqual('bar'); + // Reset data + await context.sudo().graphql.raw({ + query: `mutation { ${updateMutationName}(where: { id: "${invalidId}" }, data: { name: "Hello" }) { id name } }`, + }); } else { - expect(_item[fieldName]).toEqual(null); + expectNoAccess(data, errors, updateMutationName); } - } - }); - }); - }); - }); - }); - - describe('update', () => { - (['imperative', 'declarative'] as const).forEach(mode => { - describe(mode, () => { - listAccessVariations - .filter(({ update }) => update) - .forEach(access => { - test(`denies missing: ${JSON.stringify(access)}`, async () => { - const updateMutationName = `update${nameFn[mode](access)}`; - const query = `mutation { ${updateMutationName}(where: { id: "${FAKE_ID[provider]}" }, data: { name: "bar" }) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectNoAccess(data, errors, updateMutationName); - }); + }); - test(`denies by declarative: ${JSON.stringify(access)}`, async () => { - const updateMutationName = `update${nameFn[mode](access)}`; - const singleQueryName = nameFn[mode](access); - const invalidId = items[singleQueryName].find(({ name }) => name !== 'Hello')?.id; - const query = `mutation { ${updateMutationName}(where: { id: "${invalidId}" }, data: { name: "bar" }) { id name } }`; - const { data, errors } = await context.graphql.raw({ query }); - if (mode === 'imperative') { - expect(errors).toBe(undefined); - expect(data?.[updateMutationName]).not.toBe(null); - expect(data?.[updateMutationName].id).toEqual(invalidId); - expect(data?.[updateMutationName].name).toEqual('bar'); + test(`allows: ${JSON.stringify(access)}`, async () => { + const updateMutationName = `update${nameFn[mode](access)}`; + const singleQueryName = nameFn[mode](access); + const listKey = nameFn[mode](access); + const validId = items[singleQueryName].find(({ name }) => name === 'Hello')?.id; + const item = await context.lists[listKey].updateOne({ + where: { id: validId }, + data: { name: 'bar' }, + query: 'id name', + }); + expect(item).not.toBe(null); + expect(item.id).toEqual(validId); + expect(item.name).toEqual('bar'); // Reset data await context.sudo().graphql.raw({ - query: `mutation { ${updateMutationName}(where: { id: "${invalidId}" }, data: { name: "Hello" }) { id name } }`, + query: `mutation { ${updateMutationName}(where: { id: "${validId}" }, data: { name: "Hello" }) { id name } }`, }); - } else { - expectNoAccess(data, errors, updateMutationName); - } - }); - - test(`allows: ${JSON.stringify(access)}`, async () => { - const updateMutationName = `update${nameFn[mode](access)}`; - const singleQueryName = nameFn[mode](access); - const listKey = nameFn[mode](access); - const validId = items[singleQueryName].find(({ name }) => name === 'Hello')?.id; - const item = await context.lists[listKey].updateOne({ - where: { id: validId }, - data: { name: 'bar' }, - query: 'id name', - }); - expect(item).not.toBe(null); - expect(item.id).toEqual(validId); - expect(item.name).toEqual('bar'); - // Reset data - await context.sudo().graphql.raw({ - query: `mutation { ${updateMutationName}(where: { id: "${validId}" }, data: { name: "Hello" }) { id name } }`, }); }); - }); + }); }); }); - (['static'] as const).forEach(mode => { - describe(mode, () => { - fieldMatrix - .filter(({ update }) => update) - .forEach(access => { - test(`field allowed: ${JSON.stringify(access)}`, async () => { - const listAccess = { - create: true, - read: true, - update: true, - delete: true, - }; - const listKey = nameFn[mode](listAccess); - const item = items[listKey][0]; - const fieldName = getFieldName(access); - const _item = await context.lists[listKey].updateOne({ - where: { id: item.id }, - data: { [fieldName]: 'bar' }, - query: `id ${access.read ? fieldName : ''}`, - }); - expect(_item).not.toBe(null); - expect(_item.id).not.toBe(null); - if (access.read) { - expect(_item[fieldName]).toBe('bar'); - } else { - expect(_item[fieldName]).toBe(undefined); - } - }); - }); - }); - }); - (['imperative'] as const).forEach(mode => { - describe(mode, () => { - fieldMatrix - .filter(({ update }) => update) - .forEach(access => { - test(`field allowed: ${JSON.stringify(access)}`, async () => { - const listAccess = { create: true, read: true, update: true, delete: true }; - const listKey = nameFn[mode](listAccess); - const item = items[listKey][0]; - const fieldName = getFieldName(access); - const _item = await context.lists[listKey].updateOne({ - where: { id: item.id }, - data: { [fieldName]: 'bar' }, - }); - expect(_item).not.toBe(null); - expect(_item.id).not.toBe(null); - }); - }); - }); - }); - }); - describe('delete', () => { - (['imperative', 'declarative'] as const).forEach(mode => { - describe(mode, () => { - listAccessVariations - .filter(access => access.delete) - .forEach(access => { - const create = async (data: { name: string }) => - context.sudo().lists[nameFn[mode](access)].createOne({ data }); + describe('delete', () => { + (['imperative', 'declarative'] as const).forEach(mode => { + describe(mode, () => { + listAccessVariations + .filter(access => access.delete) + .forEach(access => { + const create = async (data: { name: string }) => + context.sudo().lists[nameFn[mode](access)].createOne({ data }); - test(`single allowed: ${JSON.stringify(access)}`, async () => { - const { id } = await create({ name: 'Hello' }); - const deleted = await context.lists[nameFn[mode](access)].deleteOne({ - where: { id }, + test(`single allowed: ${JSON.stringify(access)}`, async () => { + const { id } = await create({ name: 'Hello' }); + const deleted = await context.lists[nameFn[mode](access)].deleteOne({ + where: { id }, + }); + expect(deleted).not.toBe(null); + expect(deleted!.id).toEqual(id); + }); + + test(`single denies: ${JSON.stringify(access)}`, async () => { + const { id: invalidId } = await create({ name: 'hi' }); + const deleteMutationName = `delete${nameFn[mode](access)}`; + const query = `mutation { ${deleteMutationName}(where: { id: "${invalidId}" }) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + if (mode === 'imperative') { + expect(errors).toBe(undefined); + expect(data?.[deleteMutationName]).not.toBe(null); + expect(data?.[deleteMutationName].id).toEqual(invalidId); + } else { + expectNoAccess(data, errors, deleteMutationName); + } }); - expect(deleted).not.toBe(null); - expect(deleted!.id).toEqual(id); - }); - test(`single denies: ${JSON.stringify(access)}`, async () => { - const { id: invalidId } = await create({ name: 'hi' }); - const deleteMutationName = `delete${nameFn[mode](access)}`; - const query = `mutation { ${deleteMutationName}(where: { id: "${invalidId}" }) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - if (mode === 'imperative') { - expect(errors).toBe(undefined); - expect(data?.[deleteMutationName]).not.toBe(null); - expect(data?.[deleteMutationName].id).toEqual(invalidId); - } else { + test(`single denies missing: ${JSON.stringify(access)}`, async () => { + const deleteMutationName = `delete${nameFn[mode](access)}`; + const query = `mutation { ${deleteMutationName}(where: { id: "${FAKE_ID[provider]}" }) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); expectNoAccess(data, errors, deleteMutationName); - } - }); + }); - test(`single denies missing: ${JSON.stringify(access)}`, async () => { - const deleteMutationName = `delete${nameFn[mode](access)}`; - const query = `mutation { ${deleteMutationName}(where: { id: "${FAKE_ID[provider]}" }) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectNoAccess(data, errors, deleteMutationName); - }); + test(`multi allowed: ${JSON.stringify(access)}`, async () => { + const { id: validId1 } = await create({ name: 'Hello' }); + const { id: validId2 } = await create({ name: 'Hello' }); + const multiDeleteMutationName = `delete${nameFn[mode](access)}s`; + const query = `mutation { ${multiDeleteMutationName}(where: [{ id: "${validId1}" }, { id: "${validId2}" }]) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + expectNamedArray(data, errors, multiDeleteMutationName, [validId1, validId2]); + }); - test(`multi allowed: ${JSON.stringify(access)}`, async () => { - const { id: validId1 } = await create({ name: 'Hello' }); - const { id: validId2 } = await create({ name: 'Hello' }); - const multiDeleteMutationName = `delete${nameFn[mode](access)}s`; - const query = `mutation { ${multiDeleteMutationName}(where: [{ id: "${validId1}" }, { id: "${validId2}" }]) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectNamedArray(data, errors, multiDeleteMutationName, [validId1, validId2]); - }); + test(`multi denies: ${JSON.stringify(access)}`, async () => { + const { id: validId1 } = await create({ name: 'hi' }); + const { id: validId2 } = await create({ name: 'hi' }); + const multiDeleteMutationName = `delete${nameFn[mode](access)}s`; + const query = `mutation { ${multiDeleteMutationName}(where: [{ id: "${validId1}" }, { id: "${validId2}" }]) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + if (mode === 'imperative') { + expectNamedArray(data, errors, multiDeleteMutationName, [validId1, validId2]); + } else { + expectAccessDenied('dev', false, undefined, errors, [ + { path: [multiDeleteMutationName, 0] }, + { path: [multiDeleteMutationName, 1] }, + ]); + expect(data).toEqual({ [multiDeleteMutationName]: [null, null] }); + } + }); - test(`multi denies: ${JSON.stringify(access)}`, async () => { - const { id: validId1 } = await create({ name: 'hi' }); - const { id: validId2 } = await create({ name: 'hi' }); - const multiDeleteMutationName = `delete${nameFn[mode](access)}s`; - const query = `mutation { ${multiDeleteMutationName}(where: [{ id: "${validId1}" }, { id: "${validId2}" }]) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - if (mode === 'imperative') { - expectNamedArray(data, errors, multiDeleteMutationName, [validId1, validId2]); - } else { + test(`multi mixed allows/denies: ${JSON.stringify(access)}`, async () => { + const { id: validId1 } = await create({ name: 'Hello' }); + const { id: invalidId } = await create({ name: 'hi' }); + const multiDeleteMutationName = `delete${nameFn[mode](access)}s`; + const query = `mutation { ${multiDeleteMutationName}(where: [{ id: "${validId1}" }, { id: "${invalidId}" }]) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + if (mode === 'imperative') { + expectNamedArray(data, errors, multiDeleteMutationName, [validId1, invalidId]); + } else { + expectAccessDenied('dev', false, undefined, errors, [ + { path: [multiDeleteMutationName, 1] }, + ]); + expect(data).toEqual({ [multiDeleteMutationName]: [{ id: validId1 }, null] }); + } + }); + + test(`multi denies missing: ${JSON.stringify(access)}`, async () => { + const multiDeleteMutationName = `delete${nameFn[mode](access)}s`; + const query = `mutation { ${multiDeleteMutationName}(where: [{ id: "${FAKE_ID[provider]}" }, { id: "${FAKE_ID_2[provider]}" }]) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); expectAccessDenied('dev', false, undefined, errors, [ { path: [multiDeleteMutationName, 0] }, { path: [multiDeleteMutationName, 1] }, ]); expect(data).toEqual({ [multiDeleteMutationName]: [null, null] }); - } + }); }); + }); + }); + }); + }); - test(`multi mixed allows/denies: ${JSON.stringify(access)}`, async () => { - const { id: validId1 } = await create({ name: 'Hello' }); - const { id: invalidId } = await create({ name: 'hi' }); - const multiDeleteMutationName = `delete${nameFn[mode](access)}s`; - const query = `mutation { ${multiDeleteMutationName}(where: [{ id: "${validId1}" }, { id: "${invalidId}" }]) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - if (mode === 'imperative') { - expectNamedArray(data, errors, multiDeleteMutationName, [validId1, invalidId]); - } else { - expectAccessDenied('dev', false, undefined, errors, [ - { path: [multiDeleteMutationName, 1] }, - ]); - expect(data).toEqual({ [multiDeleteMutationName]: [{ id: validId1 }, null] }); - } + describe('Field access', () => { + describe('create', () => { + (['static'] as const).forEach(mode => { + describe(mode, () => { + fieldMatrix + .filter(({ create }) => create) + .forEach(access => { + test(`field allowed: ${JSON.stringify(access)}`, async () => { + const listAccess = { create: true, read: true, update: true, delete: true }; + const listKey = nameFn[mode](listAccess); + const fieldName = getFieldName(access); + const item = await context.lists[listKey].createOne({ + data: { [fieldName]: 'bar' }, + query: `id ${access.read ? fieldName : ''}`, + }); + expect(item).not.toBe(null); + expect(item.id).not.toBe(null); + if (access.read) { + expect(item[fieldName]).toBe('bar'); + } else { + expect(item[fieldName]).toBe(undefined); + } + await context.sudo().lists[listKey].deleteOne({ where: { id: item.id } }); + }); + }); + }); + }); + (['imperative'] as const).forEach(mode => { + describe(mode, () => { + fieldMatrix + .filter(({ create }) => create) + .forEach(access => { + test(`field allowed: ${JSON.stringify(access)}`, async () => { + const listAccess = { create: true, read: true, update: true, delete: true }; + const listKey = nameFn[mode](listAccess); + const fieldName = getFieldName(access); + const item = await context.lists[listKey].createOne({ + data: { [fieldName]: 'bar' }, + }); + expect(item).not.toBe(null); + expect(item.id).not.toBe(null); + await context.sudo().lists[listKey].deleteOne({ where: { id: item.id } }); + }); }); + }); + }); + }); - test(`multi denies missing: ${JSON.stringify(access)}`, async () => { - const multiDeleteMutationName = `delete${nameFn[mode](access)}s`; - const query = `mutation { ${multiDeleteMutationName}(where: [{ id: "${FAKE_ID[provider]}" }, { id: "${FAKE_ID_2[provider]}" }]) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectAccessDenied('dev', false, undefined, errors, [ - { path: [multiDeleteMutationName, 0] }, - { path: [multiDeleteMutationName, 1] }, - ]); - expect(data).toEqual({ [multiDeleteMutationName]: [null, null] }); + describe('read', () => { + (['imperative', 'static'] as const).forEach(mode => { + describe(mode, () => { + fieldMatrix + .filter(({ read }) => read) + .forEach(access => { + test(`field allowed - singular: ${JSON.stringify(access)}`, async () => { + const listAccess = { + create: true, + read: true, + update: true, + delete: true, + }; + const listKey = nameFn[mode](listAccess); + const item = items[listKey][0]; + const fieldName = getFieldName(access); + await context.sudo().lists[listKey].updateOne({ + where: { id: item.id }, + data: { [fieldName]: 'hello' }, + }); + const _item = await context.lists[listKey].findOne({ + where: { id: item.id }, + query: `id ${fieldName}`, + }); + expect(_item).not.toBe(null); + expect(_item.id).not.toBe(null); + expect(_item[fieldName]).toBe('hello'); + }); + test(`field allowed - multi: ${JSON.stringify(access)}`, async () => { + const listAccess = { create: true, read: true, update: true, delete: true }; + const listKey = nameFn[mode](listAccess); + const item = items[listKey][0]; + const fieldName = getFieldName(access); + await context.sudo().lists[listKey].updateOne({ + where: { id: item.id }, + data: { [fieldName]: 'hello' }, + }); + const _items = await context.lists[listKey].findMany({ query: `id ${fieldName}` }); + expect(_items).not.toBe(null); + expect(_items).toHaveLength(2); + for (const _item of _items) { + expect(_item.id).not.toBe(null); + if (_item.id === item.id) { + expect(_item[fieldName]).toEqual('hello'); + } else { + expect(_item[fieldName]).toEqual(null); + } + } + }); + }); + }); + }); + }); + + describe('update', () => { + (['static'] as const).forEach(mode => { + describe(mode, () => { + fieldMatrix + .filter(({ update }) => update) + .forEach(access => { + test(`field allowed: ${JSON.stringify(access)}`, async () => { + const listAccess = { + create: true, + read: true, + update: true, + delete: true, + }; + const listKey = nameFn[mode](listAccess); + const item = items[listKey][0]; + const fieldName = getFieldName(access); + const _item = await context.lists[listKey].updateOne({ + where: { id: item.id }, + data: { [fieldName]: 'bar' }, + query: `id ${access.read ? fieldName : ''}`, + }); + expect(_item).not.toBe(null); + expect(_item.id).not.toBe(null); + if (access.read) { + expect(_item[fieldName]).toBe('bar'); + } else { + expect(_item[fieldName]).toBe(undefined); + } + }); + }); + }); + }); + (['imperative'] as const).forEach(mode => { + describe(mode, () => { + fieldMatrix + .filter(({ update }) => update) + .forEach(access => { + test(`field allowed: ${JSON.stringify(access)}`, async () => { + const listAccess = { create: true, read: true, update: true, delete: true }; + const listKey = nameFn[mode](listAccess); + const item = items[listKey][0]; + const fieldName = getFieldName(access); + const _item = await context.lists[listKey].updateOne({ + where: { id: item.id }, + data: { [fieldName]: 'bar' }, + }); + expect(_item).not.toBe(null); + expect(_item.id).not.toBe(null); + }); }); - }); + }); }); }); }); diff --git a/tests/api-tests/access-control/not-authed.test.ts b/tests/api-tests/access-control/not-authed.test.ts index 672273684c6..0ae5df7807e 100644 --- a/tests/api-tests/access-control/not-authed.test.ts +++ b/tests/api-tests/access-control/not-authed.test.ts @@ -58,332 +58,347 @@ describe(`Not authed`, () => { await testEnv.disconnect(); }); - describe('create', () => { - (['imperative'] as const).forEach(mode => { - describe(mode, () => { - listAccessVariations - .filter(({ create }) => !create) - .forEach(access => { - test(`denied: ${JSON.stringify(access)}`, async () => { - const createMutationName = `create${nameFn[mode](access)}`; - const query = `mutation { ${createMutationName}(data: { name: "bar" }) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectNoAccess(data, errors, createMutationName); + describe('List access', () => { + describe('create', () => { + (['imperative'] as const).forEach(mode => { + describe(mode, () => { + listAccessVariations + .filter(({ create }) => !create) + .forEach(access => { + test(`denied: ${JSON.stringify(access)}`, async () => { + const createMutationName = `create${nameFn[mode](access)}`; + const query = `mutation { ${createMutationName}(data: { name: "bar" }) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + expectNoAccess(data, errors, createMutationName); + }); }); - }); + }); }); }); - (['static'] as const).forEach(mode => { - describe(mode, () => { - fieldMatrix - .filter(({ create }) => !create) - .forEach(access => { - test(`field not allowed: ${JSON.stringify(access)}`, async () => { - const listAccess = { - create: true, - read: true, - update: true, - delete: true, - }; - const createMutationName = `create${nameFn[mode](listAccess)}`; - const fieldName = getFieldName(access); - const query = `mutation { ${createMutationName}(data: { ${fieldName}: "bar" }) { id ${fieldName} } }`; - const { body } = await graphQLRequest({ query }); - // If create is not allowed on a field then there will be a query validation error - if (access.read) { - const message = `Field "${fieldName}" is not defined by type "${nameFn[mode]( - listAccess - )}CreateInput".`; - expectGraphQLValidationError(body.errors, [ - { message: expect.stringContaining(message) }, - ]); - } else { - expectGraphQLValidationError(body.errors, [ - { - message: expect.stringContaining( - `Field "${fieldName}" is not defined by type "${nameFn[mode]( - listAccess - )}CreateInput".` - ), - }, - { - message: expect.stringContaining( - `Cannot query field "${fieldName}" on type "${nameFn[mode](listAccess)}".` - ), - }, - ]); - } - expect(body.data).toBe(undefined); + describe('read', () => { + (['imperative', 'declarative'] as const).forEach(mode => { + describe(mode, () => { + listAccessVariations + .filter(({ read }) => !read) + .forEach(access => { + test(`'all' denied: ${JSON.stringify(access)}`, async () => { + const allQueryName = context.gqlNames(nameFn[mode](access)).listQueryName; + const query = `query { ${allQueryName} { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + expectNoAccess(data, errors, allQueryName); + }); + + test(`count denied: ${JSON.stringify(access)}`, async () => { + const countName = `${ + nameFn[mode](access).slice(0, 1).toLowerCase() + nameFn[mode](access).slice(1) + }sCount`; + const query = `query { ${countName} }`; + const { data, errors } = await context.graphql.raw({ query }); + expect(data).toEqual({ [countName]: null }); + expectAccessDenied('dev', false, undefined, errors, [{ path: [countName] }]); + }); + + test(`single denied: ${JSON.stringify(access)}`, async () => { + const singleQueryName = context.gqlNames(nameFn[mode](access)).itemQueryName; + const query = `query { ${singleQueryName}(where: { id: "cabc123" }) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + expectNoAccess(data, errors, singleQueryName); + }); }); - }); + }); }); }); - (['imperative'] as const).forEach(mode => { - describe(mode, () => { - fieldMatrix - .filter(({ create }) => !create) - .forEach(access => { - test(`field not allowed: ${JSON.stringify(access)}`, async () => { - const listAccess = { - create: true, - read: true, - update: true, - delete: true, - }; - const createMutationName = `create${nameFn[mode](listAccess)}`; - const fieldName = getFieldName(access); - const query = `mutation { ${createMutationName}(data: { ${fieldName}: "bar" }) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expect(data).toEqual({ [createMutationName]: null }); - expectAccessDenied('dev', false, undefined, errors, [{ path: [createMutationName] }]); + + describe('update', () => { + (['imperative', 'declarative'] as const).forEach(mode => { + describe(mode, () => { + listAccessVariations + .filter(({ update }) => !update) + .forEach(access => { + test(`denies: ${JSON.stringify(access)}`, async () => { + const updateMutationName = `update${nameFn[mode](access)}`; + const query = `mutation { ${updateMutationName}(where: { id: "${FAKE_ID[provider]}" }, data: { name: "bar" }) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + expectNoAccess(data, errors, updateMutationName); + }); }); - }); + }); }); }); - }); - describe('read', () => { - (['imperative', 'declarative'] as const).forEach(mode => { - describe(mode, () => { - listAccessVariations - .filter(({ read }) => !read) - .forEach(access => { - test(`'all' denied: ${JSON.stringify(access)}`, async () => { - const allQueryName = context.gqlNames(nameFn[mode](access)).listQueryName; - const query = `query { ${allQueryName} { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectNoAccess(data, errors, allQueryName); - }); + describe('delete', () => { + (['imperative', 'declarative'] as const).forEach(mode => { + describe(mode, () => { + listAccessVariations + .filter(access => !access.delete) + .forEach(access => { + test(`single denied: ${JSON.stringify(access)}`, async () => { + const deleteMutationName = `delete${nameFn[mode](access)}`; + const query = `mutation { ${deleteMutationName}(where: {id: "${FAKE_ID[provider]}" }) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + expectNoAccess(data, errors, deleteMutationName); + }); - test(`count denied: ${JSON.stringify(access)}`, async () => { - const countName = `${ - nameFn[mode](access).slice(0, 1).toLowerCase() + nameFn[mode](access).slice(1) - }sCount`; - const query = `query { ${countName} }`; - const { data, errors } = await context.graphql.raw({ query }); - expect(data).toEqual({ [countName]: null }); - expectAccessDenied('dev', false, undefined, errors, [{ path: [countName] }]); - }); + test(`multi denied: ${JSON.stringify(access)}`, async () => { + const multiDeleteMutationName = `delete${nameFn[mode](access)}s`; + const query = `mutation { ${multiDeleteMutationName}(where: [{ id: "${FAKE_ID[provider]}" }]) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); - test(`single denied: ${JSON.stringify(access)}`, async () => { - const singleQueryName = context.gqlNames(nameFn[mode](access)).itemQueryName; - const query = `query { ${singleQueryName}(where: { id: "cabc123" }) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectNoAccess(data, errors, singleQueryName); + expect(data).toEqual({ [multiDeleteMutationName]: [null] }); + expectAccessDenied('dev', false, undefined, errors, [ + { path: [multiDeleteMutationName, 0] }, + ]); + }); }); - }); + }); }); }); - (['imperative'] as const).forEach(mode => { - describe(mode, () => { - fieldMatrix - .filter(({ read }) => !read) - .forEach(access => { - test(`field allowed - singular: ${JSON.stringify(access)}`, async () => { - const listAccess = { - create: true, - read: true, - update: true, - delete: true, - }; - const listKey = nameFn[mode](listAccess); - const item = items[listKey][0]; - const fieldName = getFieldName(access); - const singleQueryName = context.gqlNames(listKey).itemQueryName; - await context.sudo().lists[listKey].updateOne({ - where: { id: item.id }, - data: { [fieldName]: 'hello' }, + }); + + describe('Field access', () => { + describe('create', () => { + (['static'] as const).forEach(mode => { + describe(mode, () => { + fieldMatrix + .filter(({ create }) => !create) + .forEach(access => { + test(`field not allowed: ${JSON.stringify(access)}`, async () => { + const listAccess = { + create: true, + read: true, + update: true, + delete: true, + }; + const createMutationName = `create${nameFn[mode](listAccess)}`; + const fieldName = getFieldName(access); + const query = `mutation { ${createMutationName}(data: { ${fieldName}: "bar" }) { id ${fieldName} } }`; + const { body } = await graphQLRequest({ query }); + // If create is not allowed on a field then there will be a query validation error + if (access.read) { + const message = `Field "${fieldName}" is not defined by type "${nameFn[mode]( + listAccess + )}CreateInput".`; + expectGraphQLValidationError(body.errors, [ + { message: expect.stringContaining(message) }, + ]); + } else { + expectGraphQLValidationError(body.errors, [ + { + message: expect.stringContaining( + `Field "${fieldName}" is not defined by type "${nameFn[mode]( + listAccess + )}CreateInput".` + ), + }, + { + message: expect.stringContaining( + `Cannot query field "${fieldName}" on type "${nameFn[mode](listAccess)}".` + ), + }, + ]); + } + expect(body.data).toBe(undefined); }); - const query = `query { ${singleQueryName}(where: { id: "${item.id}" }) { id ${fieldName} } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectAccessDenied('dev', false, undefined, errors, [ - { path: [singleQueryName, fieldName] }, - ]); - expect(data).toEqual({ [singleQueryName]: { id: item.id, [fieldName]: null } }); }); - test(`field allowed - multi: ${JSON.stringify(access)}`, async () => { - const listAccess = { - create: true, - read: true, - update: true, - delete: true, - }; - const listKey = nameFn[mode](listAccess); - const item = items[listKey][0]; - const fieldName = getFieldName(access); - const allQueryName = context.gqlNames(listKey).listQueryName; - await context.sudo().lists[listKey].updateOne({ - where: { id: item.id }, - data: { [fieldName]: 'hello' }, - }); - const query = `query { ${allQueryName} { id ${fieldName} } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectAccessDenied('dev', false, undefined, errors, [ - { path: [allQueryName, 0, fieldName] }, - { path: [allQueryName, 1, fieldName] }, - ]); - expect(data).toEqual({ - [allQueryName]: [ - { id: expect.any(String), [fieldName]: null }, - { id: expect.any(String), [fieldName]: null }, - ], + }); + }); + (['imperative'] as const).forEach(mode => { + describe(mode, () => { + fieldMatrix + .filter(({ create }) => !create) + .forEach(access => { + test(`field not allowed: ${JSON.stringify(access)}`, async () => { + const listAccess = { + create: true, + read: true, + update: true, + delete: true, + }; + const createMutationName = `create${nameFn[mode](listAccess)}`; + const fieldName = getFieldName(access); + const query = `mutation { ${createMutationName}(data: { ${fieldName}: "bar" }) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + expect(data).toEqual({ [createMutationName]: null }); + expectAccessDenied('dev', false, undefined, errors, [ + { path: [createMutationName] }, + ]); }); }); - }); + }); }); }); - (['static'] as const).forEach(mode => { - describe(mode, () => { - fieldMatrix - .filter(({ read }) => !read) - .forEach(access => { - test(`field allowed - singular: ${JSON.stringify(access)}`, async () => { - const listAccess = { create: true, read: true, update: true, delete: true }; - const listKey = nameFn[mode](listAccess); - const item = items[listKey][0]; - const fieldName = getFieldName(access); - const singleQueryName = context.gqlNames(listKey).itemQueryName; - await context.sudo().lists[listKey].updateOne({ - where: { id: item.id }, - data: { [fieldName]: 'hello' }, + + describe('read', () => { + (['imperative'] as const).forEach(mode => { + describe(mode, () => { + fieldMatrix + .filter(({ read }) => !read) + .forEach(access => { + test(`field allowed - singular: ${JSON.stringify(access)}`, async () => { + const listAccess = { + create: true, + read: true, + update: true, + delete: true, + }; + const listKey = nameFn[mode](listAccess); + const item = items[listKey][0]; + const fieldName = getFieldName(access); + const singleQueryName = context.gqlNames(listKey).itemQueryName; + await context.sudo().lists[listKey].updateOne({ + where: { id: item.id }, + data: { [fieldName]: 'hello' }, + }); + const query = `query { ${singleQueryName}(where: { id: "${item.id}" }) { id ${fieldName} } }`; + const { data, errors } = await context.graphql.raw({ query }); + expectAccessDenied('dev', false, undefined, errors, [ + { path: [singleQueryName, fieldName] }, + ]); + expect(data).toEqual({ [singleQueryName]: { id: item.id, [fieldName]: null } }); }); - const query = `query { ${singleQueryName}(where: { id: "${item.id}" }) { id ${fieldName} } }`; - const { body } = await graphQLRequest({ query }); - expectGraphQLValidationError(body.errors, [ - { - message: expect.stringContaining( - `Cannot query field "${fieldName}" on type "${listKey}".` - ), - }, - ]); - expect(body.data).toBe(undefined); - }); - test(`field allowed - multi: ${JSON.stringify(access)}`, async () => { - const listAccess = { create: true, read: true, update: true, delete: true }; - const listKey = nameFn[mode](listAccess); - const item = items[listKey][0]; - const fieldName = getFieldName(access); - const allQueryName = context.gqlNames(listKey).listQueryName; - await context.sudo().lists[listKey].updateOne({ - where: { id: item.id }, - data: { [fieldName]: 'hello' }, + test(`field allowed - multi: ${JSON.stringify(access)}`, async () => { + const listAccess = { + create: true, + read: true, + update: true, + delete: true, + }; + const listKey = nameFn[mode](listAccess); + const item = items[listKey][0]; + const fieldName = getFieldName(access); + const allQueryName = context.gqlNames(listKey).listQueryName; + await context.sudo().lists[listKey].updateOne({ + where: { id: item.id }, + data: { [fieldName]: 'hello' }, + }); + const query = `query { ${allQueryName} { id ${fieldName} } }`; + const { data, errors } = await context.graphql.raw({ query }); + expectAccessDenied('dev', false, undefined, errors, [ + { path: [allQueryName, 0, fieldName] }, + { path: [allQueryName, 1, fieldName] }, + ]); + expect(data).toEqual({ + [allQueryName]: [ + { id: expect.any(String), [fieldName]: null }, + { id: expect.any(String), [fieldName]: null }, + ], + }); }); - const query = `query { ${allQueryName} { id ${fieldName} } }`; - const { body } = await graphQLRequest({ query }); - expectGraphQLValidationError(body.errors, [ - { - message: expect.stringContaining( - `Cannot query field "${fieldName}" on type "${listKey}".` - ), - }, - ]); - expect(body.data).toBe(undefined); }); - }); + }); }); - }); - }); - - describe('update', () => { - (['imperative', 'declarative'] as const).forEach(mode => { - describe(mode, () => { - listAccessVariations - .filter(({ update }) => !update) - .forEach(access => { - test(`denies: ${JSON.stringify(access)}`, async () => { - const updateMutationName = `update${nameFn[mode](access)}`; - const query = `mutation { ${updateMutationName}(where: { id: "${FAKE_ID[provider]}" }, data: { name: "bar" }) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectNoAccess(data, errors, updateMutationName); + (['static'] as const).forEach(mode => { + describe(mode, () => { + fieldMatrix + .filter(({ read }) => !read) + .forEach(access => { + test(`field allowed - singular: ${JSON.stringify(access)}`, async () => { + const listAccess = { create: true, read: true, update: true, delete: true }; + const listKey = nameFn[mode](listAccess); + const item = items[listKey][0]; + const fieldName = getFieldName(access); + const singleQueryName = context.gqlNames(listKey).itemQueryName; + await context.sudo().lists[listKey].updateOne({ + where: { id: item.id }, + data: { [fieldName]: 'hello' }, + }); + const query = `query { ${singleQueryName}(where: { id: "${item.id}" }) { id ${fieldName} } }`; + const { body } = await graphQLRequest({ query }); + expectGraphQLValidationError(body.errors, [ + { + message: expect.stringContaining( + `Cannot query field "${fieldName}" on type "${listKey}".` + ), + }, + ]); + expect(body.data).toBe(undefined); + }); + test(`field allowed - multi: ${JSON.stringify(access)}`, async () => { + const listAccess = { create: true, read: true, update: true, delete: true }; + const listKey = nameFn[mode](listAccess); + const item = items[listKey][0]; + const fieldName = getFieldName(access); + const allQueryName = context.gqlNames(listKey).listQueryName; + await context.sudo().lists[listKey].updateOne({ + where: { id: item.id }, + data: { [fieldName]: 'hello' }, + }); + const query = `query { ${allQueryName} { id ${fieldName} } }`; + const { body } = await graphQLRequest({ query }); + expectGraphQLValidationError(body.errors, [ + { + message: expect.stringContaining( + `Cannot query field "${fieldName}" on type "${listKey}".` + ), + }, + ]); + expect(body.data).toBe(undefined); + }); }); - }); + }); }); }); - (['static'] as const).forEach(mode => { - describe(mode, () => { - fieldMatrix - .filter(({ update }) => !update) - .forEach(access => { - test(`field not allowed: ${JSON.stringify(access)}`, async () => { - const listAccess = { - create: true, - read: true, - update: true, - delete: true, - }; - const updateMutationName = `update${nameFn[mode](listAccess)}`; - const listKey = nameFn[mode](listAccess); - const item = items[listKey][0]; - const fieldName = getFieldName(access); - const query = `mutation { ${updateMutationName}(where: { id: "${ - item.id - }" }, data: { ${fieldName}: "bar" }) { id ${access.read ? fieldName : ''} } }`; - const { body } = await graphQLRequest({ query }); - // If update is not allowed on a field then there will be a query validation error - expectGraphQLValidationError(body.errors, [ - { - message: expect.stringContaining( - `Field "${fieldName}" is not defined by type "${listKey}UpdateInput".` - ), - }, - ]); - expect(body.data).toBe(undefined); - }); - }); - }); - }); - (['imperative'] as const).forEach(mode => { - describe(mode, () => { - fieldMatrix - .filter(({ update }) => !update) - .forEach(access => { - test(`field not allowed: ${JSON.stringify(access)}`, async () => { - const listAccess = { - create: true, - read: true, - update: true, - delete: true, - }; - const updateMutationName = `update${nameFn[mode](listAccess)}`; - const listKey = nameFn[mode](listAccess); - const item = items[listKey][0]; - const fieldName = getFieldName(access); - const query = `mutation { ${updateMutationName}(where: { id: "${item.id}" }, data: { ${fieldName}: "bar" }) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expect(data).toEqual({ [updateMutationName]: null }); - expectAccessDenied('dev', false, undefined, errors, [{ path: [updateMutationName] }]); + describe('update', () => { + (['static'] as const).forEach(mode => { + describe(mode, () => { + fieldMatrix + .filter(({ update }) => !update) + .forEach(access => { + test(`field not allowed: ${JSON.stringify(access)}`, async () => { + const listAccess = { + create: true, + read: true, + update: true, + delete: true, + }; + const updateMutationName = `update${nameFn[mode](listAccess)}`; + const listKey = nameFn[mode](listAccess); + const item = items[listKey][0]; + const fieldName = getFieldName(access); + const query = `mutation { ${updateMutationName}(where: { id: "${ + item.id + }" }, data: { ${fieldName}: "bar" }) { id ${access.read ? fieldName : ''} } }`; + const { body } = await graphQLRequest({ query }); + // If update is not allowed on a field then there will be a query validation error + expectGraphQLValidationError(body.errors, [ + { + message: expect.stringContaining( + `Field "${fieldName}" is not defined by type "${listKey}UpdateInput".` + ), + }, + ]); + expect(body.data).toBe(undefined); + }); }); - }); + }); }); - }); - }); - - describe('delete', () => { - (['imperative', 'declarative'] as const).forEach(mode => { - describe(mode, () => { - listAccessVariations - .filter(access => !access.delete) - .forEach(access => { - test(`single denied: ${JSON.stringify(access)}`, async () => { - const deleteMutationName = `delete${nameFn[mode](access)}`; - const query = `mutation { ${deleteMutationName}(where: {id: "${FAKE_ID[provider]}" }) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - expectNoAccess(data, errors, deleteMutationName); - }); - - test(`multi denied: ${JSON.stringify(access)}`, async () => { - const multiDeleteMutationName = `delete${nameFn[mode](access)}s`; - const query = `mutation { ${multiDeleteMutationName}(where: [{ id: "${FAKE_ID[provider]}" }]) { id } }`; - const { data, errors } = await context.graphql.raw({ query }); - - expect(data).toEqual({ [multiDeleteMutationName]: [null] }); - expectAccessDenied('dev', false, undefined, errors, [ - { path: [multiDeleteMutationName, 0] }, - ]); + (['imperative'] as const).forEach(mode => { + describe(mode, () => { + fieldMatrix + .filter(({ update }) => !update) + .forEach(access => { + test(`field not allowed: ${JSON.stringify(access)}`, async () => { + const listAccess = { + create: true, + read: true, + update: true, + delete: true, + }; + const updateMutationName = `update${nameFn[mode](listAccess)}`; + const listKey = nameFn[mode](listAccess); + const item = items[listKey][0]; + const fieldName = getFieldName(access); + const query = `mutation { ${updateMutationName}(where: { id: "${item.id}" }, data: { ${fieldName}: "bar" }) { id } }`; + const { data, errors } = await context.graphql.raw({ query }); + expect(data).toEqual({ [updateMutationName]: null }); + expectAccessDenied('dev', false, undefined, errors, [ + { path: [updateMutationName] }, + ]); + }); }); - }); + }); }); }); });