From d6e32e13e6cc053967316bd018707e2c557742e6 Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Wed, 6 Nov 2024 08:50:23 -0800 Subject: [PATCH] Add NodeHttpClient and FetchHttpClient tests for retry logic (#1154) ## Description - Adds tests for `NodeHttpClient` and `FetchHttpClient` to test network retries for the FGA module - Removes retry test cases from FGA module unit tests ## Documentation Does this require changes to the WorkOS Docs? E.g. the [API Reference](https://workos.com/docs/reference) or code snippets need updates. ``` [ ] Yes ``` If yes, link a related docs PR and add a docs maintainer as a reviewer. Their approval is required. --- package.json | 1 + src/common/net/fetch-client.spec.ts | 227 +++++++++ src/common/net/node-client.spec.ts | 199 ++++++++ src/fga/fga.spec.ts | 692 ---------------------------- 4 files changed, 427 insertions(+), 692 deletions(-) create mode 100644 src/common/net/fetch-client.spec.ts create mode 100644 src/common/net/node-client.spec.ts diff --git a/package.json b/package.json index 81417970a..2969645a8 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "jest": "29.6.2", "jest-environment-miniflare": "^2.14.2", "jest-fetch-mock": "^3.0.3", + "nock": "^13.5.5", "prettier": "2.8.8", "supertest": "6.3.3", "ts-jest": "29.1.3", diff --git a/src/common/net/fetch-client.spec.ts b/src/common/net/fetch-client.spec.ts new file mode 100644 index 000000000..3f7affb89 --- /dev/null +++ b/src/common/net/fetch-client.spec.ts @@ -0,0 +1,227 @@ +import fetch from 'jest-fetch-mock'; +import { fetchOnce, fetchURL } from '../../common/utils/test-utils'; +import { FetchHttpClient } from './fetch-client'; + +const fetchClient = new FetchHttpClient('https://test.workos.com', { + headers: { + Authorization: `Bearer sk_test`, + 'User-Agent': 'test-fetch-client', + }, +}); + +describe('Fetch client', () => { + beforeEach(() => fetch.resetMocks()); + + describe('fetchRequestWithRetry', () => { + it('get for FGA path should call fetchRequestWithRetry and return response', async () => { + fetchOnce({ data: 'response' }); + const mockFetchRequestWithRetry = jest.spyOn( + FetchHttpClient.prototype as any, + 'fetchRequestWithRetry', + ); + + const response = await fetchClient.get('/fga/v1/resources', {}); + + expect(mockFetchRequestWithRetry).toHaveBeenCalledTimes(1); + expect(fetchURL()).toBe('https://test.workos.com/fga/v1/resources'); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('post for FGA path should call fetchRequestWithRetry and return response', async () => { + fetchOnce({ data: 'response' }); + const mockFetchRequestWithRetry = jest.spyOn( + FetchHttpClient.prototype as any, + 'fetchRequestWithRetry', + ); + + const response = await fetchClient.post('/fga/v1/resources', {}, {}); + + expect(mockFetchRequestWithRetry).toHaveBeenCalledTimes(1); + expect(fetchURL()).toBe('https://test.workos.com/fga/v1/resources'); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('put for FGA path should call fetchRequestWithRetry and return response', async () => { + fetchOnce({ data: 'response' }); + const mockFetchRequestWithRetry = jest.spyOn( + FetchHttpClient.prototype as any, + 'fetchRequestWithRetry', + ); + + const response = await fetchClient.put( + '/fga/v1/resources/user/user-1', + {}, + {}, + ); + + expect(mockFetchRequestWithRetry).toHaveBeenCalledTimes(1); + expect(fetchURL()).toBe( + 'https://test.workos.com/fga/v1/resources/user/user-1', + ); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('delete for FGA path should call fetchRequestWithRetry and return response', async () => { + fetchOnce({ data: 'response' }); + const mockFetchRequestWithRetry = jest.spyOn( + FetchHttpClient.prototype as any, + 'fetchRequestWithRetry', + ); + + const response = await fetchClient.delete( + '/fga/v1/resources/user/user-1', + {}, + ); + + expect(mockFetchRequestWithRetry).toHaveBeenCalledTimes(1); + expect(fetchURL()).toBe( + 'https://test.workos.com/fga/v1/resources/user/user-1', + ); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('should retry request on 500 status code', async () => { + fetchOnce( + {}, + { + status: 500, + }, + ); + fetchOnce({ data: 'response' }); + const mockShouldRetryRequest = jest.spyOn( + FetchHttpClient.prototype as any, + 'shouldRetryRequest', + ); + const mockSleep = jest.spyOn(fetchClient, 'sleep'); + mockSleep.mockImplementation(() => Promise.resolve()); + + const response = await fetchClient.get('/fga/v1/resources', {}); + + expect(mockShouldRetryRequest).toHaveBeenCalledTimes(2); + expect(mockSleep).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('should retry request on 502 status code', async () => { + fetchOnce( + {}, + { + status: 502, + }, + ); + fetchOnce({ data: 'response' }); + const mockShouldRetryRequest = jest.spyOn( + FetchHttpClient.prototype as any, + 'shouldRetryRequest', + ); + const mockSleep = jest.spyOn(fetchClient, 'sleep'); + mockSleep.mockImplementation(() => Promise.resolve()); + + const response = await fetchClient.get('/fga/v1/resources', {}); + + expect(mockShouldRetryRequest).toHaveBeenCalledTimes(2); + expect(mockSleep).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('should retry request on 504 status code', async () => { + fetchOnce( + {}, + { + status: 504, + }, + ); + fetchOnce({ data: 'response' }); + const mockShouldRetryRequest = jest.spyOn( + FetchHttpClient.prototype as any, + 'shouldRetryRequest', + ); + const mockSleep = jest.spyOn(fetchClient, 'sleep'); + mockSleep.mockImplementation(() => Promise.resolve()); + + const response = await fetchClient.get('/fga/v1/resources', {}); + + expect(mockShouldRetryRequest).toHaveBeenCalledTimes(2); + expect(mockSleep).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('should retry request up to 3 times on retryable status code', async () => { + fetchOnce( + {}, + { + status: 500, + }, + ); + fetchOnce( + {}, + { + status: 502, + }, + ); + fetchOnce( + {}, + { + status: 504, + }, + ); + fetchOnce( + {}, + { + status: 504, + }, + ); + const mockShouldRetryRequest = jest.spyOn( + FetchHttpClient.prototype as any, + 'shouldRetryRequest', + ); + const mockSleep = jest.spyOn(fetchClient, 'sleep'); + mockSleep.mockImplementation(() => Promise.resolve()); + + await expect( + fetchClient.get('/fga/v1/resources', {}), + ).rejects.toThrowError('Gateway Timeout'); + + expect(mockShouldRetryRequest).toHaveBeenCalledTimes(4); + expect(mockSleep).toHaveBeenCalledTimes(3); + }); + + it('should not retry requests and throw error with non-retryable status code', async () => { + fetchOnce( + {}, + { + status: 400, + }, + ); + const mockShouldRetryRequest = jest.spyOn( + FetchHttpClient.prototype as any, + 'shouldRetryRequest', + ); + + await expect( + fetchClient.get('/fga/v1/resources', {}), + ).rejects.toThrowError('Bad Request'); + + expect(mockShouldRetryRequest).toHaveBeenCalledTimes(1); + }); + + it('should retry request on TypeError', async () => { + fetchOnce({ data: 'response' }); + const mockFetchRequest = jest.spyOn( + FetchHttpClient.prototype as any, + 'fetchRequest', + ); + mockFetchRequest.mockImplementationOnce(() => { + throw new TypeError('Network request failed'); + }); + const mockSleep = jest.spyOn(fetchClient, 'sleep'); + mockSleep.mockImplementation(() => Promise.resolve()); + + const response = await fetchClient.get('/fga/v1/resources', {}); + + expect(mockFetchRequest).toHaveBeenCalledTimes(2); + expect(mockSleep).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + }); +}); diff --git a/src/common/net/node-client.spec.ts b/src/common/net/node-client.spec.ts new file mode 100644 index 000000000..37dfcb88f --- /dev/null +++ b/src/common/net/node-client.spec.ts @@ -0,0 +1,199 @@ +import nock from 'nock'; +import { NodeHttpClient } from './node-client'; + +const nodeClient = new NodeHttpClient('https://test.workos.com', { + headers: { + Authorization: `Bearer sk_test`, + 'User-Agent': 'test-node-client', + }, +}); + +describe('Node client', () => { + beforeEach(() => nock.cleanAll()); + + it('get for FGA path should call nodeRequestWithRetry and return response', async () => { + nock('https://test.workos.com') + .get('/fga/v1/resources') + .reply(200, { data: 'response' }); + const mockNodeRequestWithRetry = jest.spyOn( + NodeHttpClient.prototype as any, + 'nodeRequestWithRetry', + ); + + const response = await nodeClient.get('/fga/v1/resources', {}); + + expect(mockNodeRequestWithRetry).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('post for FGA path should call nodeRequestWithRetry and return response', async () => { + nock('https://test.workos.com') + .post('/fga/v1/resources') + .reply(200, { data: 'response' }); + const mockNodeRequestWithRetry = jest.spyOn( + NodeHttpClient.prototype as any, + 'nodeRequestWithRetry', + ); + + const response = await nodeClient.post('/fga/v1/resources', {}, {}); + + expect(mockNodeRequestWithRetry).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('put for FGA path should call nodeRequestWithRetry and return response', async () => { + nock('https://test.workos.com') + .put('/fga/v1/resources/user/user-1') + .reply(200, { data: 'response' }); + const mockNodeRequestWithRetry = jest.spyOn( + NodeHttpClient.prototype as any, + 'nodeRequestWithRetry', + ); + + const response = await nodeClient.put( + '/fga/v1/resources/user/user-1', + {}, + {}, + ); + + expect(mockNodeRequestWithRetry).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('delete for FGA path should call nodeRequestWithRetry and return response', async () => { + nock('https://test.workos.com') + .delete('/fga/v1/resources/user/user-1') + .reply(200, { data: 'response' }); + const mockNodeRequestWithRetry = jest.spyOn( + NodeHttpClient.prototype as any, + 'nodeRequestWithRetry', + ); + + const response = await nodeClient.delete( + '/fga/v1/resources/user/user-1', + {}, + ); + + expect(mockNodeRequestWithRetry).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('should retry request on 500 status code', async () => { + nock('https://test.workos.com') + .get('/fga/v1/resources') + .reply(500) + .get('/fga/v1/resources') + .reply(200, { data: 'response' }); + const mockShouldRetryRequest = jest.spyOn( + NodeHttpClient.prototype as any, + 'shouldRetryRequest', + ); + const mockSleep = jest.spyOn(nodeClient, 'sleep'); + mockSleep.mockImplementation(() => Promise.resolve()); + + const response = await nodeClient.get('/fga/v1/resources', {}); + + expect(mockShouldRetryRequest).toHaveBeenCalledTimes(2); + expect(mockSleep).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('should retry request on 502 status code', async () => { + nock('https://test.workos.com') + .get('/fga/v1/resources') + .reply(502) + .get('/fga/v1/resources') + .reply(200, { data: 'response' }); + const mockShouldRetryRequest = jest.spyOn( + NodeHttpClient.prototype as any, + 'shouldRetryRequest', + ); + const mockSleep = jest.spyOn(nodeClient, 'sleep'); + mockSleep.mockImplementation(() => Promise.resolve()); + + const response = await nodeClient.get('/fga/v1/resources', {}); + + expect(mockShouldRetryRequest).toHaveBeenCalledTimes(2); + expect(mockSleep).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('should retry request on 504 status code', async () => { + nock('https://test.workos.com') + .get('/fga/v1/resources') + .reply(504) + .get('/fga/v1/resources') + .reply(200, { data: 'response' }); + const mockShouldRetryRequest = jest.spyOn( + NodeHttpClient.prototype as any, + 'shouldRetryRequest', + ); + const mockSleep = jest.spyOn(nodeClient, 'sleep'); + mockSleep.mockImplementation(() => Promise.resolve()); + + const response = await nodeClient.get('/fga/v1/resources', {}); + + expect(mockShouldRetryRequest).toHaveBeenCalledTimes(2); + expect(mockSleep).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); + + it('should retry request up to 3 times on retryable status code', async () => { + nock('https://test.workos.com') + .get('/fga/v1/resources') + .reply(504) + .get('/fga/v1/resources') + .reply(502) + .get('/fga/v1/resources') + .reply(500) + .get('/fga/v1/resources') + .reply(500); + const mockShouldRetryRequest = jest.spyOn( + NodeHttpClient.prototype as any, + 'shouldRetryRequest', + ); + const mockSleep = jest.spyOn(nodeClient, 'sleep'); + mockSleep.mockImplementation(() => Promise.resolve()); + + await expect( + nodeClient.get('/fga/v1/resources', {}), + ).rejects.toThrowError(); + + expect(mockShouldRetryRequest).toHaveBeenCalledTimes(4); + expect(mockSleep).toHaveBeenCalledTimes(3); + }); + + it('should not retry request on non-retryable status code', async () => { + nock('https://test.workos.com').get('/fga/v1/resources').reply(400); + const mockShouldRetryRequest = jest.spyOn( + NodeHttpClient.prototype as any, + 'shouldRetryRequest', + ); + + await expect( + nodeClient.get('/fga/v1/resources', {}), + ).rejects.toThrowError(); + + expect(mockShouldRetryRequest).toHaveBeenCalledTimes(1); + }); + + it('should retry request on TypeError', async () => { + nock('https://test.workos.com') + .get('/fga/v1/resources') + .replyWithError(new TypeError('Network request failed')) + .get('/fga/v1/resources') + .reply(200, { data: 'response' }); + const mockShouldRetryRequest = jest.spyOn( + NodeHttpClient.prototype as any, + 'shouldRetryRequest', + ); + const mockSleep = jest.spyOn(nodeClient, 'sleep'); + mockSleep.mockImplementation(() => Promise.resolve()); + + const response = await nodeClient.get('/fga/v1/resources', {}); + + expect(mockShouldRetryRequest).toHaveBeenCalledTimes(1); + expect(mockSleep).toHaveBeenCalledTimes(1); + expect(await response.toJSON()).toEqual({ data: 'response' }); + }); +}); diff --git a/src/fga/fga.spec.ts b/src/fga/fga.spec.ts index b1dd9e226..0d8a4ffe4 100644 --- a/src/fga/fga.spec.ts +++ b/src/fga/fga.spec.ts @@ -9,7 +9,6 @@ import { import { WorkOS } from '../workos'; import { ResourceOp, WarrantOp } from './interfaces'; -import { GenericServerException } from '../common/exceptions'; const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); @@ -43,68 +42,6 @@ describe('FGA', () => { isImplicit: false, }); }); - - it('makes check request after one retry', async () => { - fetchOnce( - {}, - { - status: 500, - }, - ); - fetchOnce({ - result: 'authorized', - is_implicit: false, - }); - const checkResult = await workos.fga.check({ - checks: [ - { - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_123', - }, - }, - ], - }); - expect(fetchURL()).toContain('/fga/v1/check'); - expect(checkResult).toMatchObject({ - result: 'authorized', - isImplicit: false, - }); - }); - - it('fails check request after max retries', async () => { - fetch.mockResponse( - JSON.stringify({ - message: 'Internal Server Error', - }), - { status: 500 }, - ); - - try { - await workos.fga.check({ - checks: [ - { - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_123', - }, - }, - ], - }); - } catch (e) { - expect(e).toBeInstanceOf(GenericServerException); - } - }, 8000); }); describe('createResource', () => { @@ -126,50 +63,6 @@ describe('FGA', () => { }); }); - it('creates resource after one retry', async () => { - fetchOnce( - {}, - { - status: 502, - }, - ); - fetchOnce({ - resource_type: 'role', - resource_id: 'admin', - }); - const resource = await workos.fga.createResource({ - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - }); - expect(fetchURL()).toContain('/fga/v1/resources'); - expect(resource).toMatchObject({ - resourceType: 'role', - resourceId: 'admin', - }); - }); - - it('fails to creates resource after max retries', async () => { - fetch.mockResponse( - JSON.stringify({ - message: 'Internal Server Error', - }), - { status: 500 }, - ); - - try { - await workos.fga.createResource({ - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - }); - } catch (e) { - expect(e).toBeInstanceOf(GenericServerException); - } - }, 8000); - it('creates resource with metadata', async () => { fetchOnce({ resource_type: 'role', @@ -214,46 +107,6 @@ describe('FGA', () => { resourceId: 'admin', }); }); - - it('gets resource after one retry', async () => { - fetchOnce( - {}, - { - status: 504, - }, - ); - fetchOnce({ - resource_type: 'role', - resource_id: 'admin', - }); - const resource = await workos.fga.getResource({ - resourceType: 'role', - resourceId: 'admin', - }); - expect(fetchURL()).toContain('/fga/v1/resources/role/admin'); - expect(resource).toMatchObject({ - resourceType: 'role', - resourceId: 'admin', - }); - }); - - it('fails to get resource after max retries', async () => { - fetch.mockResponse( - JSON.stringify({ - message: 'Internal Server Error', - }), - { status: 500 }, - ); - - try { - await workos.fga.getResource({ - resourceType: 'role', - resourceId: 'admin', - }); - } catch (e) { - expect(e).toBeInstanceOf(GenericServerException); - } - }, 8000); }); describe('listResources', () => { @@ -288,64 +141,6 @@ describe('FGA', () => { ]); }); - it('lists resources after two retries', async () => { - fetchOnce( - {}, - { - status: 502, - }, - ); - fetchOnce( - {}, - { - status: 500, - }, - ); - fetchOnce({ - data: [ - { - resource_type: 'role', - resource_id: 'admin', - }, - { - resource_type: 'role', - resource_id: 'manager', - }, - ], - list_metadata: { - before: null, - after: null, - }, - }); - const { data: resources } = await workos.fga.listResources(); - expect(fetchURL()).toContain('/fga/v1/resources'); - expect(resources).toMatchObject([ - { - resourceType: 'role', - resourceId: 'admin', - }, - { - resourceType: 'role', - resourceId: 'manager', - }, - ]); - }); - - it('fails to list resources after max retries', async () => { - fetch.mockResponse( - JSON.stringify({ - message: 'Internal Server Error', - }), - { status: 500 }, - ); - - try { - await workos.fga.listResources(); - } catch (e) { - expect(e).toBeInstanceOf(GenericServerException); - } - }, 8000); - it('sends correct params when filtering', async () => { fetchOnce({ data: [ @@ -384,40 +179,6 @@ describe('FGA', () => { expect(fetchURL()).toContain('/fga/v1/resources/role/admin'); expect(response).toBeUndefined(); }); - - it('should delete resource after one retry', async () => { - fetchOnce( - {}, - { - status: 500, - }, - ); - fetchOnce(); - const response = await workos.fga.deleteResource({ - resourceType: 'role', - resourceId: 'admin', - }); - expect(fetchURL()).toContain('/fga/v1/resources/role/admin'); - expect(response).toBeUndefined(); - }); - - it('fails to delete resource after max retries', async () => { - fetch.mockResponse( - JSON.stringify({ - message: 'Internal Server Error', - }), - { status: 500 }, - ); - - try { - await workos.fga.deleteResource({ - resourceType: 'role', - resourceId: 'admin', - }); - } catch (e) { - expect(e).toBeInstanceOf(GenericServerException); - } - }, 8000); }); describe('batchWriteResources', () => { @@ -487,118 +248,6 @@ describe('FGA', () => { ]); }); - it('batch create resources after one retry', async () => { - fetchOnce( - {}, - { - status: 500, - }, - ); - fetchOnce({ - data: [ - { - resource_type: 'role', - resource_id: 'admin', - meta: { - description: 'The admin role', - }, - }, - { - resource_type: 'role', - resource_id: 'manager', - }, - { - resource_type: 'role', - resource_id: 'employee', - }, - ], - }); - const createdResources = await workos.fga.batchWriteResources({ - op: ResourceOp.Create, - resources: [ - { - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - meta: { - description: 'The admin role', - }, - }, - { - resource: { - resourceType: 'role', - resourceId: 'manager', - }, - }, - { - resource: { - resourceType: 'role', - resourceId: 'employee', - }, - }, - ], - }); - expect(fetchURL()).toContain('/fga/v1/resources/batch'); - expect(createdResources).toMatchObject([ - { - resourceType: 'role', - resourceId: 'admin', - meta: { - description: 'The admin role', - }, - }, - { - resourceType: 'role', - resourceId: 'manager', - }, - { - resourceType: 'role', - resourceId: 'employee', - }, - ]); - }); - - it('fails to batch create resources after max retries', async () => { - fetch.mockResponse( - JSON.stringify({ - message: 'Internal Server Error', - }), - { status: 500 }, - ); - - try { - await workos.fga.batchWriteResources({ - op: ResourceOp.Create, - resources: [ - { - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - meta: { - description: 'The admin role', - }, - }, - { - resource: { - resourceType: 'role', - resourceId: 'manager', - }, - }, - { - resource: { - resourceType: 'role', - resourceId: 'employee', - }, - }, - ], - }); - } catch (e) { - expect(e).toBeInstanceOf(GenericServerException); - } - }, 8000); - it('batch delete resources', async () => { fetchOnce({ data: [ @@ -723,70 +372,6 @@ describe('FGA', () => { }); }); - it('should create warrant after one retry', async () => { - fetchOnce( - {}, - { - status: 500, - }, - ); - fetchOnce({ - warrant_token: 'some_token', - }); - const warrantToken = await workos.fga.writeWarrant({ - op: WarrantOp.Create, - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_123', - }, - }); - expect(fetchURL()).toContain('/fga/v1/warrants'); - expect(fetchBody()).toEqual({ - op: 'create', - resource_type: 'role', - resource_id: 'admin', - relation: 'member', - subject: { - resource_type: 'user', - resource_id: 'user_123', - }, - }); - expect(warrantToken).toMatchObject({ - warrantToken: 'some_token', - }); - }); - - it('fails to create warrant after max retries', async () => { - fetch.mockResponse( - JSON.stringify({ - message: 'Internal Server Error', - }), - { status: 500 }, - ); - - try { - await workos.fga.writeWarrant({ - op: WarrantOp.Create, - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_123', - }, - }); - } catch (e) { - expect(e).toBeInstanceOf(GenericServerException); - } - }, 8000); - it('should delete warrant with delete op', async () => { fetchOnce({ warrant_token: 'some_token', @@ -898,141 +483,6 @@ describe('FGA', () => { warrantToken: 'some_token', }); }); - - it('should batch write warrants after one retry', async () => { - fetchOnce( - {}, - { - status: 500, - }, - ); - fetchOnce({ - warrant_token: 'some_token', - }); - const warrantToken = await workos.fga.batchWriteWarrants([ - { - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_123', - }, - }, - { - op: WarrantOp.Create, - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_124', - }, - }, - { - op: WarrantOp.Delete, - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_125', - }, - }, - ]); - expect(fetchURL()).toContain('/fga/v1/warrants'); - expect(fetchBody()).toEqual([ - { - resource_type: 'role', - resource_id: 'admin', - relation: 'member', - subject: { - resource_type: 'user', - resource_id: 'user_123', - }, - }, - { - op: 'create', - resource_type: 'role', - resource_id: 'admin', - relation: 'member', - subject: { - resource_type: 'user', - resource_id: 'user_124', - }, - }, - { - op: 'delete', - resource_type: 'role', - resource_id: 'admin', - relation: 'member', - subject: { - resource_type: 'user', - resource_id: 'user_125', - }, - }, - ]); - expect(warrantToken).toMatchObject({ - warrantToken: 'some_token', - }); - }); - - it('fails to batch write warrants after max retries', async () => { - fetch.mockResponse( - JSON.stringify({ - message: 'Internal Server Error', - }), - { status: 500 }, - ); - - try { - await workos.fga.batchWriteWarrants([ - { - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_123', - }, - }, - { - op: WarrantOp.Create, - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_124', - }, - }, - { - op: WarrantOp.Delete, - resource: { - resourceType: 'role', - resourceId: 'admin', - }, - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_125', - }, - }, - ]); - } catch (e) { - expect(e).toBeInstanceOf(GenericServerException); - } - }, 8000); }); describe('listWarrants', () => { @@ -1089,80 +539,6 @@ describe('FGA', () => { ]); }); - it('should list warrants after one retry', async () => { - fetchOnce( - {}, - { - status: 500, - }, - ); - fetchOnce({ - data: [ - { - resource_type: 'role', - resource_id: 'admin', - relation: 'member', - subject: { - resource_type: 'user', - resource_id: 'user_123', - }, - }, - { - resource_type: 'role', - resource_id: 'admin', - relation: 'member', - subject: { - resource_type: 'user', - resource_id: 'user_124', - }, - policy: 'region == "us"', - }, - ], - list_metadata: { - before: null, - after: null, - }, - }); - const { data: warrants } = await workos.fga.listWarrants(); - expect(fetchURL()).toContain('/fga/v1/warrants'); - expect(warrants).toMatchObject([ - { - resourceType: 'role', - resourceId: 'admin', - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_123', - }, - }, - { - resourceType: 'role', - resourceId: 'admin', - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_124', - }, - policy: 'region == "us"', - }, - ]); - }); - - it('fails to list warrants after max retries', async () => { - fetch.mockResponse( - JSON.stringify({ - message: 'Internal Server Error', - }), - { status: 500 }, - ); - - try { - await workos.fga.listWarrants(); - } catch (e) { - expect(e).toBeInstanceOf(GenericServerException); - } - }, 8000); - it('sends correct params when filtering', async () => { fetchOnce({ data: [ @@ -1240,74 +616,6 @@ describe('FGA', () => { ]); }); - it('makes query request after one retry', async () => { - fetchOnce( - {}, - { - status: 500, - }, - ); - fetchOnce({ - data: [ - { - resource_type: 'role', - resource_id: 'admin', - warrant: { - resource_type: 'role', - resource_id: 'admin', - relation: 'member', - subject: { - resource_type: 'user', - resource_id: 'user_123', - }, - }, - is_implicit: false, - }, - ], - list_metadata: { - before: null, - after: null, - }, - }); - const { data: queryResults } = await workos.fga.query({ - q: 'select role where user:user_123 is member', - }); - expect(fetchURL()).toContain('/fga/v1/query'); - expect(queryResults).toMatchObject([ - { - resourceType: 'role', - resourceId: 'admin', - warrant: { - resourceType: 'role', - resourceId: 'admin', - relation: 'member', - subject: { - resourceType: 'user', - resourceId: 'user_123', - }, - }, - isImplicit: false, - }, - ]); - }); - - it('fails to make query after max retries', async () => { - fetch.mockResponse( - JSON.stringify({ - message: 'Internal Server Error', - }), - { status: 500 }, - ); - - try { - await workos.fga.query({ - q: 'select role where user:user_123 is member', - }); - } catch (e) { - expect(e).toBeInstanceOf(GenericServerException); - } - }, 8000); - it('sends correct params and options', async () => { fetchOnce({ data: [