-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Utility class to produce IConfigurationWire
instances
#241
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
Changes from all commits
93fcdef
7290e3a
7fc76b8
015c88a
b586bcc
4520fd2
db26eb1
4981b15
110a983
8bb9e72
4701d11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { IBanditParametersResponse, IUniversalFlagConfigResponse } from '../http-client'; | ||
import { FormatEnum } from '../interfaces'; | ||
import { getMD5Hash } from '../obfuscation'; | ||
|
||
import { ConfigurationWireHelper } from './configuration-wire-helper'; | ||
|
||
const TEST_BASE_URL = 'https://us-central1-eppo-qa.cloudfunctions.net/serveGitHubRacTestFile'; | ||
const DUMMY_SDK_KEY = 'dummy-sdk-key'; | ||
|
||
// This SDK causes the cloud endpoint below to serve the UFC test file with bandit flags. | ||
const BANDIT_SDK_KEY = 'this-key-serves-bandits'; | ||
|
||
describe('ConfigurationWireHelper', () => { | ||
describe('getBootstrapConfigurationFromApi', () => { | ||
it('should fetch obfuscated flags with android SDK', async () => { | ||
const helper = ConfigurationWireHelper.build(DUMMY_SDK_KEY, { | ||
sdkName: 'android', | ||
sdkVersion: '4.0.0', | ||
baseUrl: TEST_BASE_URL, | ||
}); | ||
|
||
const wirePacket = await helper.fetchBootstrapConfiguration(); | ||
|
||
expect(wirePacket.version).toBe(1); | ||
expect(wirePacket.config).toBeDefined(); | ||
|
||
if (!wirePacket.config) { | ||
throw new Error('Flag config not present in ConfigurationWire'); | ||
} | ||
|
||
const configResponse = JSON.parse(wirePacket.config.response) as IUniversalFlagConfigResponse; | ||
expect(configResponse.format).toBe(FormatEnum.CLIENT); | ||
expect(configResponse.flags).toBeDefined(); | ||
expect(Object.keys(configResponse.flags).length).toBeGreaterThan(1); | ||
expect(Object.keys(configResponse.flags)).toHaveLength(19); | ||
|
||
const testFlagKey = getMD5Hash('numeric_flag'); | ||
expect(Object.keys(configResponse.flags)).toContain(testFlagKey); | ||
|
||
// No bandits. | ||
expect(configResponse.banditReferences).toBeUndefined(); | ||
expect(wirePacket.bandits).toBeUndefined(); | ||
}); | ||
|
||
it('should fetch flags and bandits for node-server SDK', async () => { | ||
const helper = ConfigurationWireHelper.build(BANDIT_SDK_KEY, { | ||
sdkName: 'node-server', | ||
sdkVersion: '4.0.0', | ||
baseUrl: TEST_BASE_URL, | ||
}); | ||
|
||
const wirePacket = await helper.fetchBootstrapConfiguration(); | ||
|
||
expect(wirePacket.version).toBe(1); | ||
expect(wirePacket.config).toBeDefined(); | ||
|
||
if (!wirePacket.config) { | ||
throw new Error('Flag config not present in ConfigurationWire'); | ||
} | ||
|
||
const configResponse = JSON.parse(wirePacket.config.response) as IUniversalFlagConfigResponse; | ||
expect(configResponse.format).toBe(FormatEnum.SERVER); | ||
expect(configResponse.flags).toBeDefined(); | ||
expect(configResponse.banditReferences).toBeDefined(); | ||
expect(Object.keys(configResponse.flags)).toContain('banner_bandit_flag'); | ||
expect(Object.keys(configResponse.flags)).toContain('car_bandit_flag'); | ||
|
||
expect(wirePacket.bandits).toBeDefined(); | ||
const banditResponse = JSON.parse( | ||
wirePacket.bandits?.response ?? '', | ||
) as IBanditParametersResponse; | ||
expect(Object.keys(banditResponse.bandits).length).toBeGreaterThan(1); | ||
expect(Object.keys(banditResponse.bandits)).toContain('banner_bandit'); | ||
expect(Object.keys(banditResponse.bandits)).toContain('car_bandit'); | ||
}); | ||
|
||
it('should include fetchedAt timestamps', async () => { | ||
const helper = ConfigurationWireHelper.build(BANDIT_SDK_KEY, { | ||
sdkName: 'android', | ||
sdkVersion: '4.0.0', | ||
baseUrl: TEST_BASE_URL, | ||
}); | ||
|
||
const wirePacket = await helper.fetchBootstrapConfiguration(); | ||
|
||
if (!wirePacket.config) { | ||
throw new Error('Flag config not present in ConfigurationWire'); | ||
} | ||
if (!wirePacket.bandits) { | ||
throw new Error('Bandit config not present in ConfigurationWire'); | ||
} | ||
|
||
expect(wirePacket.config.fetchedAt).toBeDefined(); | ||
expect(Date.parse(wirePacket.config.fetchedAt ?? '')).not.toBeNaN(); | ||
expect(Date.parse(wirePacket.bandits.fetchedAt ?? '')).not.toBeNaN(); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import ApiEndpoints from '../api-endpoints'; | ||
import FetchHttpClient, { | ||
IBanditParametersResponse, | ||
IHttpClient, | ||
IUniversalFlagConfigResponse, | ||
} from '../http-client'; | ||
|
||
import { ConfigurationWireV1, IConfigurationWire } from './configuration-wire-types'; | ||
|
||
export type SdkOptions = { | ||
sdkName: string; | ||
sdkVersion: string; | ||
baseUrl?: string; | ||
}; | ||
|
||
/** | ||
* Helper class for fetching and converting configuration from the Eppo API(s). | ||
*/ | ||
export class ConfigurationWireHelper { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks extremely similar to configuration fetcher. Is there a significant difference? can this be just a new configuration fetcher? |
||
private httpClient: IHttpClient; | ||
|
||
/** | ||
* Build a new ConfigurationHelper for the target SDK Key. | ||
* @param sdkKey | ||
*/ | ||
public static build( | ||
sdkKey: string, | ||
opts: SdkOptions = { sdkName: 'android', sdkVersion: '4.0.0' }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there any reason we're choosing these values for defaults? They seem extremely off from being default |
||
) { | ||
const { sdkName, sdkVersion, baseUrl } = opts; | ||
return new ConfigurationWireHelper(sdkKey, sdkName, sdkVersion, baseUrl); | ||
} | ||
|
||
private constructor( | ||
sdkKey: string, | ||
targetSdkName = 'android', | ||
targetSdkVersion = '4.0.0', | ||
baseUrl?: string, | ||
) { | ||
const queryParams = { | ||
sdkName: targetSdkName, | ||
sdkVersion: targetSdkVersion, | ||
apiKey: sdkKey, | ||
}; | ||
const apiEndpoints = new ApiEndpoints({ | ||
baseUrl, | ||
queryParams, | ||
}); | ||
|
||
this.httpClient = new FetchHttpClient(apiEndpoints, 5000); | ||
} | ||
|
||
/** | ||
* Fetches configuration data from the API and build a Bootstrap Configuration (aka an `IConfigurationWire` object). | ||
* The IConfigurationWire instance can be used to bootstrap some SDKs. | ||
*/ | ||
public async fetchBootstrapConfiguration(): Promise<IConfigurationWire> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason we're calling it "bootstrap configuration" vs just "configuration"? When I head "bootstrap configuration" I imagine: sdk key and set of urls to fetch configuration from |
||
// Get the configs | ||
let banditResponse: IBanditParametersResponse | undefined; | ||
const configResponse: IUniversalFlagConfigResponse | undefined = | ||
await this.httpClient.getUniversalFlagConfiguration(); | ||
|
||
if (!configResponse?.flags) { | ||
console.warn('Unable to fetch configuration, returning empty configuration'); | ||
return Promise.resolve(ConfigurationWireV1.empty()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor: given the function is
|
||
} | ||
|
||
const flagsHaveBandits = Object.keys(configResponse.banditReferences ?? {}).length > 0; | ||
if (flagsHaveBandits) { | ||
banditResponse = await this.httpClient.getBanditParameters(); | ||
} | ||
|
||
return ConfigurationWireV1.fromResponses(configResponse, banditResponse); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙌