From a028e3480fd2c88561657f030af532c17e8fdc6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Thu, 19 Oct 2023 10:26:38 +0100 Subject: [PATCH 1/2] fix: accept deploy ID in `getDeployStore` --- src/main.test.ts | 94 +++++++++++++++++++++++++++++++++----------- src/store_factory.ts | 8 +++- 2 files changed, 78 insertions(+), 24 deletions(-) diff --git a/src/main.test.ts b/src/main.test.ts index 02f4dee..8136b34 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -916,19 +916,9 @@ describe('delete', () => { }) describe('Deploy scope', () => { - test('Returns a deploy-scoped store if the `deployID` parameter is supplied', async () => { + test('Returns a deploy-scoped store if the `deployID` parameter is supplied and the environment context is present', async () => { const mockToken = 'some-token' const mockStore = new MockFetch() - .get({ - headers: { authorization: `Bearer ${mockToken}` }, - response: new Response(value), - url: `${edgeURL}/${siteID}/images/${key}`, - }) - .get({ - headers: { authorization: `Bearer ${mockToken}` }, - response: new Response(value), - url: `${edgeURL}/${siteID}/images/${key}`, - }) .get({ headers: { authorization: `Bearer ${mockToken}` }, response: new Response(value), @@ -950,26 +940,52 @@ describe('Deploy scope', () => { env.NETLIFY_BLOBS_CONTEXT = Buffer.from(JSON.stringify(context)).toString('base64') - const siteStore = getStore('images') + const deployStore = getStore({ deployID }) - const string1 = await siteStore.get(key) - expect(string1).toBe(value) + const string = await deployStore.get(key) + expect(string).toBe(value) - const stream1 = await siteStore.get(key, { type: 'stream' }) - expect(await streamToString(stream1 as unknown as NodeJS.ReadableStream)).toBe(value) + const stream = await deployStore.get(key, { type: 'stream' }) + expect(await streamToString(stream as unknown as NodeJS.ReadableStream)).toBe(value) - const deployStore = getStore({ deployID }) + expect(mockStore.fulfilled).toBeTruthy() + }) - const string2 = await deployStore.get(key) - expect(string2).toBe(value) + test('Returns a deploy-scoped store if the `deployID` parameter is supplied and the environment context is not present', async () => { + const mockStore = new MockFetch() + .get({ + headers: { authorization: `Bearer ${apiToken}` }, + response: new Response(JSON.stringify({ url: signedURL })), + url: `https://api.netlify.com/api/v1/sites/${siteID}/blobs/${key}?context=deploy:${deployID}`, + }) + .get({ + response: new Response(value), + url: signedURL, + }) + .get({ + headers: { authorization: `Bearer ${apiToken}` }, + response: new Response(JSON.stringify({ url: signedURL })), + url: `https://api.netlify.com/api/v1/sites/${siteID}/blobs/${key}?context=deploy:${deployID}`, + }) + .get({ + response: new Response(value), + url: signedURL, + }) + + globalThis.fetch = mockStore.fetch - const stream2 = await deployStore.get(key, { type: 'stream' }) - expect(await streamToString(stream2 as unknown as NodeJS.ReadableStream)).toBe(value) + const deployStore = getStore({ deployID, siteID, token: apiToken }) + + const string = await deployStore.get(key) + expect(string).toBe(value) + + const stream = await deployStore.get(key, { type: 'stream' }) + expect(await streamToString(stream as unknown as NodeJS.ReadableStream)).toBe(value) expect(mockStore.fulfilled).toBeTruthy() }) - test('Returns a deploy-scoped store if the `getDeployStore` method is called', async () => { + test('Returns a deploy-scoped store if the `getDeployStore` method is called and the environment context is present', async () => { const mockToken = 'some-token' const mockStore = new MockFetch() .get({ @@ -1004,6 +1020,40 @@ describe('Deploy scope', () => { expect(mockStore.fulfilled).toBeTruthy() }) + + test('Returns a deploy-scoped store if the `getDeployStore` method is called and the environment context is not present', async () => { + const mockStore = new MockFetch() + .get({ + headers: { authorization: `Bearer ${apiToken}` }, + response: new Response(JSON.stringify({ url: signedURL })), + url: `https://api.netlify.com/api/v1/sites/${siteID}/blobs/${key}?context=deploy:${deployID}`, + }) + .get({ + response: new Response(value), + url: signedURL, + }) + .get({ + headers: { authorization: `Bearer ${apiToken}` }, + response: new Response(JSON.stringify({ url: signedURL })), + url: `https://api.netlify.com/api/v1/sites/${siteID}/blobs/${key}?context=deploy:${deployID}`, + }) + .get({ + response: new Response(value), + url: signedURL, + }) + + globalThis.fetch = mockStore.fetch + + const deployStore = getDeployStore({ deployID, siteID, token: apiToken }) + + const string = await deployStore.get(key) + expect(string).toBe(value) + + const stream = await deployStore.get(key, { type: 'stream' }) + expect(await streamToString(stream as unknown as NodeJS.ReadableStream)).toBe(value) + + expect(mockStore.fulfilled).toBeTruthy() + }) }) describe('Custom `fetch`', () => { diff --git a/src/store_factory.ts b/src/store_factory.ts index 0eb701d..ab39c89 100644 --- a/src/store_factory.ts +++ b/src/store_factory.ts @@ -2,12 +2,16 @@ import { Client, ClientOptions, getClientOptions } from './client.ts' import { getEnvironmentContext, MissingBlobsEnvironmentError } from './environment.ts' import { Store } from './store.ts' +interface GetDeployStoreOptions extends Partial { + deployID?: string +} + /** * Gets a reference to a deploy-scoped store. */ -export const getDeployStore = (options: Partial = {}): Store => { +export const getDeployStore = (options: GetDeployStoreOptions = {}): Store => { const context = getEnvironmentContext() - const { deployID } = context + const deployID = options.deployID ?? context.deployID if (!deployID) { throw new MissingBlobsEnvironmentError(['deployID']) From da4189b84666db3444007838eaca6caee59e49a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Thu, 19 Oct 2023 10:50:05 +0100 Subject: [PATCH 2/2] fix: throw when store name is missing --- src/main.test.ts | 20 ++++++++++++++++++++ src/store_factory.ts | 8 +++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/main.test.ts b/src/main.test.ts index 8136b34..a0a714b 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -1100,4 +1100,24 @@ describe(`getStore`, () => { }), ).toThrowError(MissingBlobsEnvironmentError) }) + + test('Throws when the name of the store is not provided', async () => { + const { fetch } = new MockFetch() + + globalThis.fetch = fetch + + // @ts-expect-error Ignoring types, which expect an argument + expect(() => getStore()).toThrowError( + 'The `getStore` method requires the name of the store as a string or as the `name` property of an options object', + ) + + expect(() => + getStore({ + token: apiToken, + siteID, + }), + ).toThrowError( + 'The `getStore` method requires the name of the store as a string or as the `name` property of an options object', + ) + }) }) diff --git a/src/store_factory.ts b/src/store_factory.ts index ab39c89..57db708 100644 --- a/src/store_factory.ts +++ b/src/store_factory.ts @@ -44,7 +44,7 @@ export const getStore: { return new Store({ client, name: input }) } - if (typeof input.name === 'string') { + if (typeof input?.name === 'string') { const { name } = input const clientOptions = getClientOptions(input) @@ -57,7 +57,7 @@ export const getStore: { return new Store({ client, name }) } - if (typeof input.deployID === 'string') { + if (typeof input?.deployID === 'string') { const clientOptions = getClientOptions(input) const { deployID } = input @@ -70,5 +70,7 @@ export const getStore: { return new Store({ client, deployID }) } - throw new Error('`getStore()` requires a `name` or `siteID` properties.') + throw new Error( + 'The `getStore` method requires the name of the store as a string or as the `name` property of an options object', + ) }