Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preferred locale option #359

Merged
merged 7 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/facade/contentFetcherFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ function validate(options: unknown): asserts options is FetchOptions {
}
}

export type Configuration = {
type Options = {
preferredLocale?: string,
};

export type Configuration = Options & {
contentFetcher: ContentFetcher,
contextFactory: ContextFactory,
previewTokenProvider: TokenProvider,
Expand All @@ -40,12 +44,17 @@ export class ContentFetcherFacade {

private readonly cidAssigner: CidAssigner;

private readonly options: Options;

public constructor(configuration: Configuration) {
this.fetcher = configuration.contentFetcher;
this.previewTokenProvider = configuration.previewTokenProvider;
this.userTokenProvider = configuration.userTokenProvider;
this.cidAssigner = configuration.cidAssigner;
this.contextFactory = configuration.contextFactory;
this.options = {
preferredLocale: configuration.preferredLocale,
};
}

public async fetch<P extends JsonObject>(slotId: string, options: FetchOptions = {}): Promise<FetchResponse<P>> {
Expand All @@ -61,9 +70,9 @@ export class ContentFetcherFacade {
userToken: this.userTokenProvider.getToken() ?? undefined,
previewToken: this.previewTokenProvider.getToken() ?? undefined,
version: options.version,
preferredLocale: options.preferredLocale,
context: this.contextFactory.createContext(options.attributes),
timeout: options.timeout,
preferredLocale: options.preferredLocale ?? this.options.preferredLocale,
});
}
}
17 changes: 14 additions & 3 deletions src/facade/sdkFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import {PartialTrackingEvent} from '../trackingEvents';
import {UrlSanitizer} from '../tab';
import {ContentFetcherFacade} from './contentFetcherFacade';

export type Configuration = {
type Options = {
preferredLocale?: string,
};

export type Configuration = Options & {
appId: string,
tokenScope?: TokenScope,
debug?: boolean,
Expand Down Expand Up @@ -53,14 +57,17 @@ export class SdkFacade {

private contentFetcherFacade?: ContentFetcherFacade;

private constructor(sdk: Sdk) {
private readonly options: Options;

private constructor(sdk: Sdk, options: Options = {}) {
this.sdk = sdk;
this.options = options;
}

public static init(configuration: Configuration): SdkFacade {
validateConfiguration(configuration);

const {track = true, userId, token, ...containerConfiguration} = configuration;
const {track = true, userId, token, preferredLocale, ...containerConfiguration} = configuration;

if (userId !== undefined && token !== undefined) {
throw new Error('Either the user ID or token can be specified, but not both.');
Expand All @@ -74,6 +81,9 @@ export class SdkFacade {
test: containerConfiguration.test ?? false,
disableCidMirroring: containerConfiguration.disableCidMirroring ?? false,
}),
{
preferredLocale: preferredLocale,
},
);

if (userId !== undefined) {
Expand Down Expand Up @@ -166,6 +176,7 @@ export class SdkFacade {
cidAssigner: this.sdk.cidAssigner,
previewTokenProvider: this.sdk.previewTokenStore,
userTokenProvider: this.sdk.userTokenStore,
preferredLocale: this.options.preferredLocale,
});
}

Expand Down
3 changes: 3 additions & 0 deletions src/schema/sdkFacadeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,8 @@ export const sdkFacadeConfigurationSchema = new ObjectType({
cidAssignerEndpointUrl: new StringType({
format: 'url',
}),
preferredLocale: new StringType({
pattern: /^[a-z]{2,3}([-_][a-z]{2,3})?$/i,
}),
},
});
24 changes: 19 additions & 5 deletions test/facade/contentFecherFacade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,13 @@ describe('A content fetcher facade', () => {
const userToken = Token.issue('00000000-0000-0000-0000-000000000000', 'foo', Date.now());
const previewToken = Token.issue('11111111-1111-1111-1111-111111111111', 'bar', Date.now());

const evaluationFacade = new ContentFetcherFacade({
const fetcherFacade = new ContentFetcherFacade({
contentFetcher: fetcher,
cidAssigner: new FixedAssigner(clientId),
userTokenProvider: new FixedTokenProvider(userToken),
previewTokenProvider: new FixedTokenProvider(previewToken),
contextFactory: new TabContextFactory(tab),
preferredLocale: 'pt-br',
});

const options: FetchOptions = {
Expand All @@ -109,7 +110,6 @@ describe('A content fetcher facade', () => {
previewToken: previewToken,
timeout: 5,
version: 1,
preferredLocale: 'en-US',
context: {
attributes: {
foo: 'bar',
Expand All @@ -125,13 +125,27 @@ describe('A content fetcher facade', () => {

const slotId = 'home-banner';

await evaluationFacade.fetch(slotId, {
await fetcherFacade.fetch(slotId, {
timeout: options.timeout,
version: options.version,
preferredLocale: options.preferredLocale,
attributes: options?.context?.attributes,
});

expect(fetcher.fetch).toHaveBeenNthCalledWith(1, slotId, options);
expect(fetcher.fetch).toHaveBeenNthCalledWith(1, slotId, {
...options,
preferredLocale: 'pt-br',
});

await fetcherFacade.fetch(slotId, {
timeout: options.timeout,
version: options.version,
attributes: options?.context?.attributes,
preferredLocale: 'en-us',
});

expect(fetcher.fetch).toHaveBeenNthCalledWith(2, slotId, {
...options,
preferredLocale: 'en-us',
});
});
});
16 changes: 13 additions & 3 deletions test/facade/sdkFacade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,17 +381,27 @@ describe('A SDK facade', () => {
return sdk;
});

const slotId = 'home-banner';
const preferredLocale = 'en-us';

const sdkFacade = SdkFacade.init({
appId: appId,
track: false,
preferredLocale: preferredLocale,
});

const slotId = 'home-banner';
const options: FetchOptions = {timeout: 5};
const options: FetchOptions = {
timeout: 5,
};

const expectedOptions: FetchOptions = {
...options,
preferredLocale: preferredLocale,
};

await expect(sdkFacade.contentFetcher.fetch(slotId, options)).resolves.toBe(result);

expect(fetcher.fetch).toHaveBeenCalledWith(slotId, expect.objectContaining(options));
expect(fetcher.fetch).toHaveBeenCalledWith(slotId, expect.objectContaining(expectedOptions));
expect(fetcher.fetch).toHaveBeenCalledTimes(1);
});

Expand Down
117 changes: 104 additions & 13 deletions test/schemas/sdkFacadeSchemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ describe('The SDK facade configuration schema', () => {
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
userId: null,
}],
[{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
preferredLocale: 'pt',
}],
[{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
preferredLocale: 'pt_br',
}],
[{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
preferredLocale: 'pt_BR',
}],
[{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
preferredLocale: 'pt-br',
}],
[{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
preferredLocale: 'pt-BR',
}],
[{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
preferredLocale: 'abc_cde',
}],
[{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
baseEndpointUrl: 'https://api.croct.io/',
Expand Down Expand Up @@ -54,57 +78,124 @@ describe('The SDK facade configuration schema', () => {
"Missing property '/appId'.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', clientId: '7e9d59a9'},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
clientId: '7e9d59a9',
},
"Invalid format at path '/clientId'.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', cidAssignerEndpointUrl: 'x'},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
cidAssignerEndpointUrl: 'x',
},
"Invalid url format at path '/cidAssignerEndpointUrl'.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', baseEndpointUrl: 'x'},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
baseEndpointUrl: 'x',
},
"Invalid url format at path '/baseEndpointUrl'.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', tokenScope: 'x'},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
tokenScope: 'x',
},
"Unexpected value at path '/tokenScope', expecting 'global', 'contextual' or 'isolated', found 'x'.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', userId: ''},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
userId: '',
},
"Expected at least 1 character at path '/userId', actual 0.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', userId: 1},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
userId: 1,
},
"Expected value of type string or null at path '/userId', actual integer.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', token: 'foo'},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
token: 'foo',
},
"Invalid format at path '/token'.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', token: 1},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
token: 1,
},
"Expected value of type string or null at path '/token', actual integer.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', debug: 'foo'},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
debug: 'foo',
},
"Expected value of type boolean at path '/debug', actual string.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', test: 'foo'},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
test: 'foo',
},
"Expected value of type boolean at path '/test', actual string.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', track: 'foo'},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
track: 'foo',
},
"Expected value of type boolean at path '/track', actual string.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', disableCidMirroring: 'foo'},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
disableCidMirroring: 'foo',
},
"Expected value of type boolean at path '/disableCidMirroring', actual string.",
],
[
{appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a', logger: null},
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
logger: null,
},
"Expected value of type object at path '/logger', actual null.",
],
[
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
preferredLocale: '',
},
"Invalid format at path '/preferredLocale'.",
],
[
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
preferredLocale: 'fooo',
},
'Invalid format at path \'/preferredLocale\'.',
],
[
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
preferredLocale: 'foo-baar',
},
'Invalid format at path \'/preferredLocale\'.',
],
[
{
appId: '7e9d59a9-e4b3-45d4-b1c7-48287f1e5e8a',
preferredLocale: 'foo_baar',
},
'Invalid format at path \'/preferredLocale\'.',
],
])('should not allow %s', (value: Record<string, unknown>, message: string) => {
function validate(): void {
sdkFacadeConfigurationSchema.validate(value);
Expand Down