From 7491a54ead382e22cd49a279fb2ea050e9076926 Mon Sep 17 00:00:00 2001 From: Raghd Hamzeh Date: Fri, 18 Aug 2023 15:03:05 -0400 Subject: [PATCH] fix: list relations should throw when an underlying check errors --- client.ts | 5 ++++ tests/client.test.ts | 69 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/client.ts b/client.ts index 10c2552..e2ac755 100644 --- a/client.ts +++ b/client.ts @@ -613,6 +613,11 @@ export class OpenFgaClient extends BaseAPI { contextualTuples: listRelationsRequest.contextualTuples, })), { ...options, headers, maxParallelRequests }); + const firstErrorResponse = batchCheckResults.responses.find(response => (response as any).error); + if (firstErrorResponse) { + throw (firstErrorResponse as any).error; + } + return { relations: batchCheckResults.responses.filter(result => result.allowed).map(result => result._request.relation) }; } diff --git a/tests/client.test.ts b/tests/client.test.ts index bb743e1..2451346 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -16,6 +16,7 @@ import * as nock from "nock"; import { ClientWriteStatus, CredentialsMethod, + FgaApiError, FgaApiAuthenticationError, FgaValidationError, OpenFgaClient @@ -512,11 +513,8 @@ describe("OpenFGA Client", () => { const scope0 = nocks.check(defaultConfiguration.storeId!, tuples[0], defaultConfiguration.getBasePath(), { allowed: true }).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); const scope1 = nocks.check(defaultConfiguration.storeId!, tuples[1], defaultConfiguration.getBasePath(), { allowed: false }).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); const scope2 = nocks.check(defaultConfiguration.storeId!, tuples[2], defaultConfiguration.getBasePath(), { allowed: true }).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); - const scope3 = nocks.check(defaultConfiguration.storeId!, tuples[3], defaultConfiguration.getBasePath(), "" as any, 500).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); - const scope4 = nocks.check(defaultConfiguration.storeId!, tuples[4], defaultConfiguration.getBasePath(), { - "code": "validation_error", - "message": "relation 'workspace#can_read' not found" - }, 400).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); + const scope3 = nocks.check(defaultConfiguration.storeId!, tuples[3], defaultConfiguration.getBasePath(), { allowed: false }).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); + const scope4 = nocks.check(defaultConfiguration.storeId!, tuples[4], defaultConfiguration.getBasePath(), { allowed: false }).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); const scope5 = nock(defaultConfiguration.getBasePath()) .get(`/stores/${defaultConfiguration.storeId!}/authorization-models`) .query({ page_size: 1 }) @@ -546,6 +544,67 @@ describe("OpenFGA Client", () => { expect(response.relations.sort()).toEqual(expect.arrayContaining(["admin", "reader"])); }); + it("should throw an error if any check returns an error", async () => { + const tuples = [{ + user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation: "admin", + object: "workspace:1", + }, { + user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation: "guest", + object: "workspace:1", + }, { + user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation: "reader", + object: "workspace:1", + }, { + user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation: "viewer", + object: "workspace:1", + }, { + user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation: "can_read", + object: "workspace:1", + }]; + const scope0 = nocks.check(defaultConfiguration.storeId!, tuples[0], defaultConfiguration.getBasePath(), { allowed: true }).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); + const scope1 = nocks.check(defaultConfiguration.storeId!, tuples[1], defaultConfiguration.getBasePath(), { allowed: false }).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); + const scope2 = nocks.check(defaultConfiguration.storeId!, tuples[2], defaultConfiguration.getBasePath(), { allowed: true }).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); + const scope3 = nocks.check(defaultConfiguration.storeId!, tuples[3], defaultConfiguration.getBasePath(), "" as any, 500).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); + const scope4 = nocks.check(defaultConfiguration.storeId!, tuples[4], defaultConfiguration.getBasePath(), { + "code": "validation_error", + "message": "relation 'workspace#can_read' not found" + }, 400).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); + const scope5 = nock(defaultConfiguration.getBasePath()) + .get(`/stores/${defaultConfiguration.storeId!}/authorization-models`) + .query({ page_size: 1 }) + .reply(200, { + authorization_models: [], + }); + + expect(scope0.isDone()).toBe(false); + expect(scope1.isDone()).toBe(false); + expect(scope2.isDone()).toBe(false); + expect(scope3.isDone()).toBe(false); + expect(scope4.isDone()).toBe(false); + expect(scope5.isDone()).toBe(false); + + try { + const response = await fgaClient.listRelations({ + user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + object: "workspace:1", + relations: ["admin", "guest", "reader", "viewer"], + }); + } catch (err) { + expect(scope0.isDone()).toBe(true); + expect(scope1.isDone()).toBe(true); + expect(scope2.isDone()).toBe(true); + expect(scope3.isDone()).toBe(true); + expect(scope4.isDone()).toBe(false); + expect(scope5.isDone()).toBe(true); + expect(err).toBeInstanceOf(FgaApiError); + } + }); + it("should throw an error if no relations passed", async () => { try { await fgaClient.listRelations({