Skip to content

Commit

Permalink
Merge branch 'main' into fix_gql_client_build_issue
Browse files Browse the repository at this point in the history
  • Loading branch information
paulomarg authored Nov 21, 2023
2 parents 8c36e82 + 2c63d18 commit 73085f2
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .changeset/clever-pumas-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
139 changes: 98 additions & 41 deletions packages/shopify-app-remix/src/server/__test-helpers/test-config.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,110 @@
import {LATEST_API_VERSION, LogSeverity} from '@shopify/shopify-api';
import {
LATEST_API_VERSION,
LogSeverity,
ShopifyRestResources,
} from '@shopify/shopify-api';
import {SessionStorage} from '@shopify/shopify-app-session-storage';
import {MemorySessionStorage} from '@shopify/shopify-app-session-storage-memory';

import type {AppConfigArg} from '../config-types';
import type {FutureFlags} from '../future/flags';
import type {FutureFlagOptions, FutureFlags} from '../future/flags';

import {API_KEY, API_SECRET_KEY, APP_URL} from './const';

type DefaultedFutureFlag<
Overrides extends Partial<AppConfigArg>,
Flag extends keyof FutureFlags,
> = Overrides['future'] extends FutureFlags ? Overrides['future'][Flag] : true;

type TestConfig<Overrides extends Partial<AppConfigArg>> =
// We omit billing so we use the actual values when set, rather than the generic type
Omit<AppConfigArg, 'billing'> &
Overrides & {
// Create an object with all future flags defaulted to active to ensure our tests are updated when we introduce new flags
future: {
v3_authenticatePublic: DefaultedFutureFlag<
Overrides,
'v3_authenticatePublic'
>;
v3_webhookAdminContext: DefaultedFutureFlag<
Overrides,
'v3_webhookAdminContext'
>;
};
};

export function testConfig<Overrides extends Partial<AppConfigArg>>(
overrides: Overrides = {} as Overrides,
): TestConfig<Overrides> {
/*
* This object mandates that all existing future flags be activated for tests. If a new flag is added, this object must
* be updated to include it, which will also cause all tests to use the new behaviour by default (and likely fail).
*
* This way, we'll always ensure our tests are covering all future flags. Please make sure to also have tests for the
* old behaviour.
*/
const TEST_FUTURE_FLAGS: Required<{[key in keyof FutureFlags]: true}> = {
v3_authenticatePublic: true,
v3_webhookAdminContext: true,
} as const;

const TEST_CONFIG = {
apiKey: API_KEY,
apiSecretKey: API_SECRET_KEY,
scopes: ['testScope'] as any,
apiVersion: LATEST_API_VERSION,
appUrl: APP_URL,
logger: {
log: jest.fn(),
level: LogSeverity.Debug,
},
isEmbeddedApp: true,
sessionStorage: new MemorySessionStorage(),
future: TEST_FUTURE_FLAGS,
} as const;

// Reset the config object before each test
beforeEach(() => {
TEST_CONFIG.logger.log.mockReset();
(TEST_CONFIG as any).sessionStorage = new MemorySessionStorage();
});

export function testConfig<
Overrides extends TestOverridesArg<Future>,
Future extends FutureFlagOptions,
>(
{future, ...overrides}: Overrides & {future?: Future} = {} as Overrides & {
future?: Future;
},
): TestConfig<Overrides, Future> {
return {
apiKey: API_KEY,
apiSecretKey: API_SECRET_KEY,
scopes: ['testScope'],
apiVersion: LATEST_API_VERSION,
appUrl: APP_URL,
...TEST_CONFIG,
...overrides,
logger: {
log: jest.fn(),
level: LogSeverity.Debug,
...TEST_CONFIG.logger,
...(overrides as NonNullable<Overrides>).logger,
},
isEmbeddedApp: true,
sessionStorage: new MemorySessionStorage(),
...overrides,
future: {
v3_webhookAdminContext: true,
v3_authenticatePublic: true,
...(overrides.future as Overrides['future']),
...TEST_CONFIG.future,
...future,
},
};
} as any;
}

/*
* This type combines both passed in types, by ignoring any keys from Type1 that are present (and not undefined)
* in Type2, and then adding any keys from Type1 that are not present in Type2.
*
* This effectively enables us to create a type that is the const TEST_CONFIG below, plus any overrides passed in with
* the output being a const object.
*/
type Modify<Type1, Type2> = {
[key in keyof Type2 as Type2[key] extends undefined
? never
: key]: Type2[key];
} & {
[key in keyof Type1 as key extends keyof Type2 ? never : key]: Type1[key];
};

type DefaultedFutureFlags<Future> = Modify<typeof TEST_CONFIG.future, Future>;

/*
* We omit the future prop and then redefine it with a partial of that object so we can pass the fully typed object in
* to AppConfigArg.
*/
type TestOverrides<Future> = Partial<
Omit<
AppConfigArg<
ShopifyRestResources,
SessionStorage,
DefaultedFutureFlags<Future>
>,
'future'
> & {
future: Partial<DefaultedFutureFlags<Future>>;
}
>;

type TestOverridesArg<Future> = undefined | TestOverrides<Future>;

type TestConfig<Overrides extends TestOverridesArg<Future>, Future> = Modify<
typeof TEST_CONFIG,
Overrides
> & {
future: Modify<typeof TEST_CONFIG.future, Future>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
AppDistribution,
ApiVersion,
} from '../index';
import {AppConfigArg} from '../config-types';
import {testConfig} from '../__test-helpers';

describe('shopifyApp', () => {
Expand Down Expand Up @@ -111,10 +110,10 @@ describe('shopifyApp', () => {

it('fails if no session storage is given', () => {
// GIVEN
const config: AppConfigArg = testConfig({sessionStorage: undefined});
const config = testConfig({sessionStorage: undefined});
delete (config as any).sessionStorage;

// THEN
expect(() => shopifyApp(config)).toThrowError(ShopifyError);
expect(() => shopifyApp(config as any)).toThrowError(ShopifyError);
});
});
5 changes: 3 additions & 2 deletions packages/shopify-app-remix/src/server/config-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import {
} from '@shopify/shopify-api';
import {SessionStorage} from '@shopify/shopify-app-session-storage';

import type {FutureFlags} from './future/flags';
import type {FutureFlagOptions, FutureFlags} from './future/flags';
import type {AppDistribution} from './types';
import type {AdminApiContext} from './clients';

export interface AppConfigArg<
Resources extends ShopifyRestResources = ShopifyRestResources,
Storage extends SessionStorage = SessionStorage,
Future extends FutureFlagOptions = FutureFlagOptions,
> extends Omit<
ApiConfigArg<Resources>,
| 'hostName'
Expand Down Expand Up @@ -219,7 +220,7 @@ export interface AppConfigArg<
* You can opt in to these features by setting the corresponding flags. By doing so, you can prepare for future
* releases in advance and provide feedback on the new features.
*/
future?: FutureFlags;
future?: Future;
}

export interface AppConfig<Storage extends SessionStorage = SessionStorage>
Expand Down
2 changes: 2 additions & 0 deletions packages/shopify-app-remix/src/server/future/flags.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// When adding new flags, you should also add them to the `TEST_FUTURE_FLAGS` object in `test-config.ts` to ensure that
// it doesn't cause regressions.
export interface FutureFlags {
/**
* When enabled, returns the same `admin` context (`AdminApiContext`) from `authenticate.webhook` that is returned from `authenticate.admin`.
Expand Down

0 comments on commit 73085f2

Please sign in to comment.