diff --git a/src/adapters/automaton.ts b/src/adapters/automaton.ts new file mode 100644 index 0000000..2fe54d1 --- /dev/null +++ b/src/adapters/automaton.ts @@ -0,0 +1,79 @@ +import type { RoutingOptions } from '../utils/router'; + +import { Router } from '../utils'; + +export type Automata = 'ADD_USER_TO_ALL_GROUPS' | 'REMOVE_USER_FROM_ALL_GROUPS'; + +export type AutomatonStatus = 'EXECUTING' | 'COMPLETED' | 'FAILED'; + +export type AutomatonParameterType = 'string' | 'date' | 'long' | 'double'; + +export interface AutomatonParameter { + objectType: AutomatonParameterType; + value: string | number | null; +} + +export interface AutomatonParameters { + [key: string]: AutomatonParameter; +} + +export interface AutomatedReadOutView { + jobId: number | null; + error: string | null; + status: AutomatonStatus | null; +} + + +/** + * Starts an automaton job for a given project. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/automaton/{AUTOMATA}` + * + * @example + * import { automatonAdapter } from 'epicenter-libs'; + * const job = await automatonAdapter.start('ADD_USER_TO_ALL_GROUPS', { + * userId: { objectType: 'string', value: 'user123' }, + * }); + * + * @param automata The automaton action to execute + * @param parameters Parameters for the automaton job, keyed by parameter name + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.addNonce] If true, adds a nonce to the request + * @returns promise that resolves to the automaton job status + */ +export async function start( + automata: Automata, + parameters: AutomatonParameters, + optionals: { + addNonce?: boolean; + } & RoutingOptions = {}, +): Promise { + const { addNonce, ...routingOptions } = optionals; + return await new Router() + .withSearchParams({ addNonce }) + .post(`/automaton/${automata}`, { + body: { parameters }, + ...routingOptions, + }).then(({ body }) => body); +} + + +/** + * Gets the status of an automaton job. + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/automaton/{JOB_ID}` + * + * @example + * import { automatonAdapter } from 'epicenter-libs'; + * const status = await automatonAdapter.getStatus(12345); + * + * @param jobId The job ID returned from starting an automaton + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves to the automaton job status + */ +export async function getStatus( + jobId: number, + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .get(`/automaton/${jobId}`, optionals) + .then(({ body }) => body); +} diff --git a/src/adapters/context.ts b/src/adapters/context.ts new file mode 100644 index 0000000..35e1f22 --- /dev/null +++ b/src/adapters/context.ts @@ -0,0 +1,117 @@ +import type { RoutingOptions } from '../utils/router'; +import type { ModelLanguage, Morphology } from '../utils/constants'; + +import { Router } from '../utils'; + +export interface StellaModelTool { + objectType: 'stella'; + gameMode?: boolean | null; +} + +export interface VensimModelTool { + objectType: 'vensim'; + sensitivityMode?: boolean | null; + cinFiles?: unknown[] | null; +} + +export type ModelTool = StellaModelTool | VensimModelTool; + +export interface V1ExecutionContext { + version: unknown; + presets?: Record | null; + mappedFiles?: Record | null; + tool?: ModelTool; +} + +export interface ModelContext { + [key: string]: unknown; +} + + +/** + * Gets the model context for a given model file. + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/context/{MODEL_FILE}` + * + * @example + * import { contextAdapter } from 'epicenter-libs'; + * const context = await contextAdapter.get('model.vmf'); + * + * @param modelFile The model file name + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.morphology] The morphology type for the model context + * @returns promise that resolves to the model context + */ +export async function get( + modelFile: string, + optionals: { + morphology?: Morphology; + } & RoutingOptions = {}, +): Promise { + const { morphology, ...routingOptions } = optionals; + return await new Router() + .withSearchParams({ morphology }) + .get(`/context/${modelFile}`, routingOptions) + .then(({ body }) => body); +} + + +/** + * Upgrades the model context for a given model file. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/context/upgrade/{MODEL_FILE}` + * + * @example + * import { contextAdapter } from 'epicenter-libs'; + * await contextAdapter.upgrade('model.vmf', { morphology: 'SINGULAR' }); + * + * @param modelFile The model file name + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.morphology] The morphology type for the upgrade + * @returns promise that resolves to void on success + */ +export async function upgrade( + modelFile: string, + optionals: { + morphology?: Morphology; + } & RoutingOptions = {}, +): Promise { + const { morphology, ...routingOptions } = optionals; + return await new Router() + .withSearchParams({ morphology }) + .post(`/context/upgrade/${modelFile}`, routingOptions) + .then(({ body }) => body); +} + + +/** + * Verifies a model context configuration. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/context/verify` + * + * @example + * import { contextAdapter } from 'epicenter-libs'; + * await contextAdapter.verify('model.vmf', { + * modelLanguage: 'VENSIM', + * morphology: 'SINGULAR', + * }); + * + * @param modelFile The model file name + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.morphology] The morphology type + * @param [optionals.modelLanguage] The model language + * @param [optionals.executionContext] The execution context configuration + * @returns promise that resolves to void on success + */ +export async function verify( + modelFile: string, + optionals: { + morphology?: Morphology; + modelLanguage?: ModelLanguage; + executionContext?: V1ExecutionContext; + } & RoutingOptions = {}, +): Promise { + const { morphology, modelLanguage, executionContext, ...routingOptions } = optionals; + return await new Router() + .post('/context/verify', { + body: { modelFile, morphology, modelLanguage, executionContext }, + ...routingOptions, + }).then(({ body }) => body); +} diff --git a/src/adapters/dashboard.ts b/src/adapters/dashboard.ts new file mode 100644 index 0000000..7010820 --- /dev/null +++ b/src/adapters/dashboard.ts @@ -0,0 +1,252 @@ +import type { RoutingOptions } from '../utils/router'; + +import { Router } from '../utils'; + +export type DashboardVersion = 'V1'; + +export type PopType = 'first' | 'last' | 'all'; + +export interface Pop { + objectType: PopType; + value?: object; +} + +export interface Items { + set?: Record | null; + push?: Record | null; + inc?: Record | null; + pop?: Record | null; +} + +export interface DashboardPreferenceReadOutView { + projectKey: string | null; + adminKey: string | null; + items: Record; + version: DashboardVersion | null; + changed: boolean | null; +} + + +/** + * Gets a dashboard preference by admin key. + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/dashboard/preference/{VERSION}/{ADMIN_KEY}` + * + * @example + * import { dashboardAdapter } from 'epicenter-libs'; + * const preference = await dashboardAdapter.get('my-admin-key'); + * + * @param adminKey The admin key identifying the preference + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.version] The dashboard version; defaults to 'V1' + * @returns promise that resolves to the dashboard preference + */ +export async function get( + adminKey: string, + optionals: { + version?: DashboardVersion; + } & RoutingOptions = {}, +): Promise { + const { version = 'V1', ...routingOptions } = optionals; + return await new Router() + .get(`/dashboard/preference/${version}/${adminKey}`, routingOptions) + .then(({ body }) => body); +} + + +/** + * Creates a dashboard preference with an admin key. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/dashboard/preference/{VERSION}/{ADMIN_KEY}` + * + * @example + * import { dashboardAdapter } from 'epicenter-libs'; + * const preference = await dashboardAdapter.create('my-admin-key', { + * items: { set: { theme: { dark: true } } }, + * }); + * + * @param adminKey The admin key identifying the preference + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.items] Items to set on the preference + * @param [optionals.version] The dashboard version; defaults to 'V1' + * @returns promise that resolves to the created dashboard preference + */ +export async function create( + adminKey: string, + optionals: { + items?: Items; + version?: DashboardVersion; + } & RoutingOptions = {}, +): Promise { + const { items, version = 'V1', ...routingOptions } = optionals; + return await new Router() + .post(`/dashboard/preference/${version}/${adminKey}`, { + body: { items }, + ...routingOptions, + }).then(({ body }) => body); +} + + +/** + * Updates a dashboard preference by admin key using item operations (set, push, inc, pop). + * Base URL: PUT `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/dashboard/preference/{VERSION}/{ADMIN_KEY}` + * + * @example + * import { dashboardAdapter } from 'epicenter-libs'; + * const preference = await dashboardAdapter.update('my-admin-key', { + * set: { theme: { dark: true } }, + * inc: { viewCount: 1 }, + * }); + * + * @param adminKey The admin key identifying the preference + * @param items Item operations to apply (set, push, inc, pop) + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.version] The dashboard version; defaults to 'V1' + * @returns promise that resolves to the updated dashboard preference + */ +export async function update( + adminKey: string, + items: Items, + optionals: { + version?: DashboardVersion; + } & RoutingOptions = {}, +): Promise { + const { version = 'V1', ...routingOptions } = optionals; + return await new Router() + .put(`/dashboard/preference/${version}/${adminKey}`, { + body: items, + ...routingOptions, + }).then(({ body }) => body); +} + + +/** + * Deletes a dashboard preference by admin key. + * Base URL: DELETE `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/dashboard/preference/{VERSION}/{ADMIN_KEY}` + * + * @example + * import { dashboardAdapter } from 'epicenter-libs'; + * const preference = await dashboardAdapter.remove('my-admin-key'); + * + * @param adminKey The admin key identifying the preference + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.version] The dashboard version; defaults to 'V1' + * @returns promise that resolves to the deleted dashboard preference + */ +export async function remove( + adminKey: string, + optionals: { + version?: DashboardVersion; + } & RoutingOptions = {}, +): Promise { + const { version = 'V1', ...routingOptions } = optionals; + return await new Router() + .delete(`/dashboard/preference/${version}/${adminKey}`, routingOptions) + .then(({ body }) => body); +} + + +/** + * Gets all dashboard preferences for a project. + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/dashboard/preference/{VERSION}` + * + * @example + * import { dashboardAdapter } from 'epicenter-libs'; + * const preferences = await dashboardAdapter.getAll(); + * + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.version] The dashboard version; defaults to 'V1' + * @returns promise that resolves to the dashboard preference + */ +export async function getAll( + optionals: { + version?: DashboardVersion; + } & RoutingOptions = {}, +): Promise { + const { version = 'V1', ...routingOptions } = optionals; + return await new Router() + .get(`/dashboard/preference/${version}`, routingOptions) + .then(({ body }) => body); +} + + +/** + * Creates a dashboard preference without an admin key. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/dashboard/preference/{VERSION}` + * + * @example + * import { dashboardAdapter } from 'epicenter-libs'; + * const preference = await dashboardAdapter.createDefault({ + * items: { set: { theme: { dark: true } } }, + * }); + * + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.items] Items to set on the preference + * @param [optionals.version] The dashboard version; defaults to 'V1' + * @returns promise that resolves to the created dashboard preference + */ +export async function createDefault( + optionals: { + items?: Items; + version?: DashboardVersion; + } & RoutingOptions = {}, +): Promise { + const { items, version = 'V1', ...routingOptions } = optionals; + return await new Router() + .post(`/dashboard/preference/${version}`, { + body: { items }, + ...routingOptions, + }).then(({ body }) => body); +} + + +/** + * Updates dashboard preferences for a project using item operations. + * Base URL: PUT `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/dashboard/preference/{VERSION}` + * + * @example + * import { dashboardAdapter } from 'epicenter-libs'; + * const preference = await dashboardAdapter.updateAll({ + * set: { theme: { dark: true } }, + * }); + * + * @param items Item operations to apply (set, push, inc, pop) + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.version] The dashboard version; defaults to 'V1' + * @returns promise that resolves to the updated dashboard preference + */ +export async function updateAll( + items: Items, + optionals: { + version?: DashboardVersion; + } & RoutingOptions = {}, +): Promise { + const { version = 'V1', ...routingOptions } = optionals; + return await new Router() + .put(`/dashboard/preference/${version}`, { + body: items, + ...routingOptions, + }).then(({ body }) => body); +} + + +/** + * Deletes all dashboard preferences for a project. + * Base URL: DELETE `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/dashboard/preference/{VERSION}` + * + * @example + * import { dashboardAdapter } from 'epicenter-libs'; + * const preference = await dashboardAdapter.removeAll(); + * + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.version] The dashboard version; defaults to 'V1' + * @returns promise that resolves to the deleted dashboard preference + */ +export async function removeAll( + optionals: { + version?: DashboardVersion; + } & RoutingOptions = {}, +): Promise { + const { version = 'V1', ...routingOptions } = optionals; + return await new Router() + .delete(`/dashboard/preference/${version}`, routingOptions) + .then(({ body }) => body); +} diff --git a/src/adapters/encyclopedia.ts b/src/adapters/encyclopedia.ts new file mode 100644 index 0000000..52d5aa2 --- /dev/null +++ b/src/adapters/encyclopedia.ts @@ -0,0 +1,112 @@ +import type { RoutingOptions } from '../utils/router'; + +import Router from '../utils/router'; + +export type TranslatorFormat = 'ASCIIDOC' | 'ASCIIDOC_TO_HTML' | 'OPENAPI'; + +export interface DocumentedParameter { + name: string; + type: object; + source: 'PATH' | 'HEADER' | 'QUERY' | 'MATRIX'; +} + +export interface DocumentedEndpoint { + response: object; + method: 'CONNECT' | 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT' | 'TRACE'; + authorization: 'SYSTEM' | 'DASHBOARD' | 'OWNER' | 'AUTHOR' | 'SUPPORT' | 'REVIEWER' | 'FACILITATOR' | 'LEADER' | 'PARTICIPANT' | 'ANONYMOUS'; + path: string; + summary?: string; + paged?: boolean; + notations?: ('POST_RATHER_THAN_GET')[]; + deprecated?: boolean; + description?: string; + body?: object; + produces?: unknown[]; + parameters?: DocumentedParameter[]; + consumes?: unknown[]; +} + +export interface DocumentedResource { + silenced?: boolean; + endpoints?: DocumentedEndpoint[]; + definitions?: object; +} + +export interface KnownService { + name?: string; + development?: boolean; + trusted?: boolean; + published?: boolean; +} + + +/** + * Lists all known API services for a given version. + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/encyclopedia/v{VERSION}` + * + * @example + * import { encyclopediaAdapter } from 'epicenter-libs'; + * const services = await encyclopediaAdapter.list(1); + * + * @param version The API version number + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves to a list of known services + */ +export async function list( + version: number, + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .get(`/encyclopedia/v${version}`, optionals) + .then(({ body }) => body); +} + + +/** + * Gets documentation for a specific API. + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/encyclopedia/v{VERSION}/{API}` + * + * @example + * import { encyclopediaAdapter } from 'epicenter-libs'; + * const docs = await encyclopediaAdapter.get(1, 'run'); + * + * @param version The API version number + * @param api The API name to get documentation for + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves to the documented resource + */ +export async function get( + version: number, + api: string, + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .get(`/encyclopedia/v${version}/${api}`, optionals) + .then(({ body }) => body); +} + + +/** + * Gets translated documentation for a specific API in a given format. + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/encyclopedia/as/{TRANSLATOR}/v{VERSION}/{API}` + * + * @example + * import { encyclopediaAdapter } from 'epicenter-libs'; + * await encyclopediaAdapter.translate(1, 'run', 'OPENAPI'); + * + * @param version The API version number + * @param api The API name to get documentation for + * @param translator The output format: 'ASCIIDOC', 'ASCIIDOC_TO_HTML', or 'OPENAPI' + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves when the translation is complete (204 no content) + */ +export async function translate( + version: number, + api: string, + translator: TranslatorFormat, + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .get(`/encyclopedia/as/${translator}/v${version}/${api}`, optionals) + .then(({ body }) => body); +} diff --git a/src/adapters/fido2.ts b/src/adapters/fido2.ts new file mode 100644 index 0000000..5990cf8 --- /dev/null +++ b/src/adapters/fido2.ts @@ -0,0 +1,100 @@ +import type { RoutingOptions } from '../utils/router'; + +import Router from '../utils/router'; + +export type PublicKeyCredentialRequestOptions = Record; +export type PublicKeyCredentialCreationOptions = Record; + + +/** + * Gets options for a FIDO2 authentication (assertion) request. + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/fido2/options/request` + * + * @example + * import { fido2Adapter } from 'epicenter-libs'; + * const options = await fido2Adapter.getRequestOptions(); + * + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves to the public key credential request options + */ +export async function getRequestOptions( + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .get('/fido2/options/request', { + ...optionals, + }) + .then(({ body }) => body); +} + + +/** + * Gets options for a FIDO2 registration (attestation) request. + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/fido2/options/create` + * + * @example + * import { fido2Adapter } from 'epicenter-libs'; + * const options = await fido2Adapter.getCreateOptions(); + * + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves to the public key credential creation options + */ +export async function getCreateOptions( + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .get('/fido2/options/create', { + ...optionals, + }) + .then(({ body }) => body); +} + + +/** + * Registers a new FIDO2 credential. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/fido2/register` + * + * @example + * import { fido2Adapter } from 'epicenter-libs'; + * await fido2Adapter.register(credential); + * + * @param credential The attestation credential to register + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves when registration is complete (204 no content) + */ +export async function register( + credential: Record, + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .post('/fido2/register', { + body: credential, + ...optionals, + }) + .then(({ body }) => body); +} + + +/** + * Verifies a FIDO2 assertion (authenticates with a credential). + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/fido2/verify` + * + * @example + * import { fido2Adapter } from 'epicenter-libs'; + * await fido2Adapter.verify(assertion); + * + * @param assertion The assertion credential to verify + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves when verification is complete (204 no content) + */ +export async function verify( + assertion: Record, + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .post('/fido2/verify', { + body: assertion, + ...optionals, + }) + .then(({ body }) => body); +} diff --git a/src/adapters/forge.ts b/src/adapters/forge.ts new file mode 100644 index 0000000..e33ab4e --- /dev/null +++ b/src/adapters/forge.ts @@ -0,0 +1,205 @@ +import type { RoutingOptions } from '../utils/router'; +import type { ModelLanguage, Morphology } from '../utils/constants'; + +import { Router } from '../utils'; + + +/** + * Checks if the build environment is ready for a given model language. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/forge/ready` + * + * @example + * import { forgeAdapter } from 'epicenter-libs'; + * await forgeAdapter.ready('PYTHON_3'); + * + * @param modelLanguage The model language to check readiness for + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves when the check is complete (204 no content) + */ +export async function ready( + modelLanguage: ModelLanguage, + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .post('/forge/ready', { + body: { modelLanguage }, + ...optionals, + }) + .then(({ body }) => body); +} + + +/** + * Triggers a rebuild of all models in the project. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/forge/rebuild` + * + * @example + * import { forgeAdapter } from 'epicenter-libs'; + * await forgeAdapter.rebuild(); + * + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.noCache] Whether to skip the build cache + * @param [optionals.cleanUp] Whether to clean up build artifacts + * @returns promise that resolves when the rebuild is triggered (204 no content) + */ +export async function rebuild( + optionals: { + noCache?: boolean; + cleanUp?: boolean; + } & RoutingOptions = {}, +): Promise { + const { + noCache, cleanUp, + ...routingOptions + } = optionals; + return await new Router() + .post('/forge/rebuild', { + body: { noCache, cleanUp }, + ...routingOptions, + }) + .then(({ body }) => body); +} + + +/** + * Sets the default build configuration for a specific model language. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/forge/default` + * + * @example + * import { forgeAdapter } from 'epicenter-libs'; + * await forgeAdapter.setDefault('PYTHON_3', { modelMorphology: 'SINGULAR' }); + * + * @param modelLanguage The model language to set the default for + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.noCache] Whether to skip the build cache + * @param [optionals.cleanUp] Whether to clean up build artifacts + * @param [optionals.modelMorphology] The model morphology: 'MANY', 'PROXY', or 'SINGULAR' + * @param [optionals.workerImage] Custom worker image to use + * @returns promise that resolves when the default is set (204 no content) + */ +export async function setDefault( + modelLanguage: ModelLanguage, + optionals: { + noCache?: boolean; + cleanUp?: boolean; + modelMorphology?: Morphology; + workerImage?: string; + } & RoutingOptions = {}, +): Promise { + const { + noCache, cleanUp, modelMorphology, workerImage, + ...routingOptions + } = optionals; + return await new Router() + .post('/forge/default', { + body: { modelLanguage, noCache, cleanUp, modelMorphology, workerImage }, + ...routingOptions, + }) + .then(({ body }) => body); +} + + +/** + * Sets default build configuration for all model languages. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/forge/default/all` + * + * @example + * import { forgeAdapter } from 'epicenter-libs'; + * await forgeAdapter.setDefaultAll({ noCache: true }); + * + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.noCache] Whether to skip the build cache + * @param [optionals.cleanUp] Whether to clean up build artifacts + * @returns promise that resolves when the defaults are set (204 no content) + */ +export async function setDefaultAll( + optionals: { + noCache?: boolean; + cleanUp?: boolean; + } & RoutingOptions = {}, +): Promise { + const { + noCache, cleanUp, + ...routingOptions + } = optionals; + return await new Router() + .post('/forge/default/all', { + body: { noCache, cleanUp }, + ...routingOptions, + }) + .then(({ body }) => body); +} + + +/** + * Sets default build configuration for multiple model languages. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/forge/defaults` + * + * @example + * import { forgeAdapter } from 'epicenter-libs'; + * await forgeAdapter.setDefaults(['PYTHON_3', 'JAVASCRIPT'], { modelMorphology: 'SINGULAR' }); + * + * @param modelLanguages Array of model languages to set defaults for + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.noCache] Whether to skip the build cache + * @param [optionals.cleanUp] Whether to clean up build artifacts + * @param [optionals.modelMorphology] The model morphology: 'MANY', 'PROXY', or 'SINGULAR' + * @returns promise that resolves when the defaults are set (204 no content) + */ +export async function setDefaults( + modelLanguages: ModelLanguage[], + optionals: { + noCache?: boolean; + cleanUp?: boolean; + modelMorphology?: Morphology; + } & RoutingOptions = {}, +): Promise { + const { + noCache, cleanUp, modelMorphology, + ...routingOptions + } = optionals; + return await new Router() + .post('/forge/defaults', { + body: { modelLanguages, noCache, cleanUp, modelMorphology }, + ...routingOptions, + }) + .then(({ body }) => body); +} + + +/** + * Builds a specific model file. + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/forge/build` + * + * @example + * import { forgeAdapter } from 'epicenter-libs'; + * await forgeAdapter.build('model.py', { modelLanguage: 'PYTHON_3' }); + * + * @param modelFile The model file to build + * @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist. + * @param [optionals.noCache] Whether to skip the build cache + * @param [optionals.cleanUp] Whether to clean up build artifacts + * @param [optionals.modelLanguage] The model language + * @param [optionals.modelMorphology] The model morphology: 'MANY', 'PROXY', or 'SINGULAR' + * @returns promise that resolves when the build is triggered (204 no content) + */ +export async function build( + modelFile: string, + optionals: { + noCache?: boolean; + cleanUp?: boolean; + modelLanguage?: ModelLanguage; + modelMorphology?: Morphology; + } & RoutingOptions = {}, +): Promise { + const { + noCache, cleanUp, modelLanguage, modelMorphology, + ...routingOptions + } = optionals; + return await new Router() + .post('/forge/build', { + body: { modelFile, noCache, cleanUp, modelLanguage, modelMorphology }, + ...routingOptions, + }) + .then(({ body }) => body); +} diff --git a/src/adapters/index.ts b/src/adapters/index.ts index 2f45dbe..9d8c885 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -1,57 +1,73 @@ import * as accountAdapter from './account'; import * as adminAdapter from './admin'; -import * as authAdapter from './authentication'; import * as assetAdapter from './asset'; +import * as authAdapter from './authentication'; +import * as automatonAdapter from './automaton'; +import * as chatAdapter from './chat'; +import * as consensusAdapter from './consensus'; +import * as contextAdapter from './context'; +import * as dailyAdapter from './daily'; +import * as dashboardAdapter from './dashboard'; import * as emailAdapter from './email'; +import * as encyclopediaAdapter from './encyclopedia'; import * as episodeAdapter from './episode'; +import * as fido2Adapter from './fido2'; +import * as forgeAdapter from './forge'; import * as groupAdapter from './group'; +import * as informationAdapter from './information'; import * as leaderboardAdapter from './leaderboard'; +import * as matchmakerAdapter from './matchmaker'; +import * as notificationAdapter from './notification'; import * as presenceAdapter from './presence'; import * as projectAdapter from './project'; import * as recaptchaAdapter from './recaptcha'; import * as runAdapter from './run'; +import * as somebodyAdapter from './somebody'; +import * as taskAdapter from './task'; +import * as timeAdapter from './time'; import * as userAdapter from './user'; import * as vaultAdapter from './vault'; import * as videoAdapter from './video'; import * as vonageAdapter from './vonage'; -import * as worldAdapter from './world'; -import * as timeAdapter from './time'; -import * as taskAdapter from './task'; -import * as chatAdapter from './chat'; -import * as consensusAdapter from './consensus'; -import * as somebodyAdapter from './somebody'; -import * as matchmakerAdapter from './matchmaker'; -import * as dailyAdapter from './daily'; import * as walletAdapter from './wallet'; +import * as worldAdapter from './world'; import { default as cometdAdapter } from './cometd'; import { default as Channel } from './channel'; export { accountAdapter, adminAdapter, - authAdapter, assetAdapter, + authAdapter, + automatonAdapter, + Channel, chatAdapter, cometdAdapter, + consensusAdapter, + contextAdapter, + dailyAdapter, + dashboardAdapter, emailAdapter, + encyclopediaAdapter, episodeAdapter, + fido2Adapter, + forgeAdapter, groupAdapter, + informationAdapter, leaderboardAdapter, + matchmakerAdapter, + notificationAdapter, presenceAdapter, projectAdapter, recaptchaAdapter, runAdapter, + somebodyAdapter, + taskAdapter, timeAdapter, userAdapter, vaultAdapter, videoAdapter, vonageAdapter, - worldAdapter, - taskAdapter, - consensusAdapter, - somebodyAdapter, - matchmakerAdapter, - dailyAdapter, walletAdapter, - Channel, + worldAdapter, }; diff --git a/src/adapters/information.ts b/src/adapters/information.ts new file mode 100644 index 0000000..c04e4e0 --- /dev/null +++ b/src/adapters/information.ts @@ -0,0 +1,29 @@ +import type { RoutingOptions } from '../utils/router'; + +import { Router } from '../utils'; + +export interface InformationReadOutView { + gitCommit?: string; +} + + +/** + * Retrieves build information for the current project deployment + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/information` + * + * @example + * import { informationAdapter } from 'epicenter-libs'; + * const info = await informationAdapter.get(); + * + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves to information about the deployment + */ +export async function get( + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .get('/information', { + ...optionals, + }) + .then(({ body }) => body); +} diff --git a/src/adapters/notification.ts b/src/adapters/notification.ts new file mode 100644 index 0000000..27a0108 --- /dev/null +++ b/src/adapters/notification.ts @@ -0,0 +1,123 @@ +import type { RoutingOptions } from '../utils/router'; + +import { Router } from '../utils'; + +export type NotificationType = 'MARKETING' | 'SYSTEM'; + +export interface NotificationPreferenceCreateInView { + notificationType?: NotificationType; + adminKey?: string; + notify?: boolean; +} + +export interface NotificationPreferenceReadOutView { + notificationType?: NotificationType; + ownerKey?: string; + notify?: boolean; +} + + +/** + * Sets a notification preference + * Base URL: POST `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/notification/preference` + * + * @example + * import { notificationAdapter } from 'epicenter-libs'; + * await notificationAdapter.setPreference({ + * notificationType: 'MARKETING', + * adminKey: 'my-admin-key', + * notify: false, + * }); + * + * @param preference Notification preference to set + * @param [preference.notificationType] Type of notification (MARKETING or SYSTEM) + * @param [preference.adminKey] Admin key for the preference + * @param [preference.notify] Whether to send notifications + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves to undefined if successful + */ +export async function setPreference( + preference: NotificationPreferenceCreateInView, + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .post('/notification/preference', { + body: preference, + ...optionals, + }) + .then(({ body }) => body); +} + + +/** + * Gets notification preferences for a given admin key + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/notification/preference/{ADMIN_KEY}` + * + * @example + * import { notificationAdapter } from 'epicenter-libs'; + * const preferences = await notificationAdapter.getPreferences('my-admin-key'); + * + * @param adminKey Admin key to retrieve preferences for + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves to a list of notification preferences + */ +export async function getPreferences( + adminKey: string, + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .get(`/notification/preference/${adminKey}`, { + ...optionals, + }) + .then(({ body }) => body); +} + + +/** + * Opts out of all notification types using an opt-out token + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/notification/optout/{OPT_OUT_TOKEN}` + * + * @example + * import { notificationAdapter } from 'epicenter-libs'; + * await notificationAdapter.optOut('my-opt-out-token'); + * + * @param optOutToken Token used to opt out of notifications + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves to the opt-out result + */ +export async function optOut( + optOutToken: string, + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .get(`/notification/optout/${optOutToken}`, { + ...optionals, + }) + .then(({ body }) => body); +} + + +/** + * Opts out of a specific notification type using an opt-out token + * Base URL: GET `https://forio.com/api/v3/{ACCOUNT}/{PROJECT}/notification/optout/{NOTIFICATION_TYPE}/{OPT_OUT_TOKEN}` + * + * @example + * import { notificationAdapter } from 'epicenter-libs'; + * await notificationAdapter.optOutByType('MARKETING', 'my-opt-out-token'); + * + * @param notificationType Type of notification to opt out of (MARKETING or SYSTEM) + * @param optOutToken Token used to opt out of notifications + * @param [optionals] Optional arguments; pass network call options overrides here. + * @returns promise that resolves to the opt-out result + */ +export async function optOutByType( + notificationType: NotificationType, + optOutToken: string, + optionals: RoutingOptions = {}, +): Promise { + return await new Router() + .get(`/notification/optout/${notificationType}/${optOutToken}`, { + ...optionals, + }) + .then(({ body }) => body); +} diff --git a/src/adapters/run.ts b/src/adapters/run.ts index 82a2990..f324109 100644 --- a/src/adapters/run.ts +++ b/src/adapters/run.ts @@ -233,11 +233,8 @@ export interface VensimModelTool { cinFiles?: string[]; } -export enum MORPHOLOGY { - MANY = 'MANY', - PROXY = 'PROXY', - SINGULAR = 'SINGULAR', -} +export { MORPHOLOGY } from '../utils/constants'; +export type { Morphology } from '../utils/constants'; export interface ProcActionable { name: string; diff --git a/src/epicenter.ts b/src/epicenter.ts index 1891fc4..d41fa9c 100644 --- a/src/epicenter.ts +++ b/src/epicenter.ts @@ -101,12 +101,19 @@ export { accountAdapter, adminAdapter, assetAdapter, + automatonAdapter, + contextAdapter, consensusAdapter, + dashboardAdapter, emailAdapter, + encyclopediaAdapter, authAdapter, chatAdapter, episodeAdapter, + fido2Adapter, + forgeAdapter, groupAdapter, + informationAdapter, leaderboardAdapter, presenceAdapter, projectAdapter, @@ -122,6 +129,7 @@ export { somebodyAdapter, dailyAdapter, matchmakerAdapter, + notificationAdapter, walletAdapter, Channel, cometdAdapter, diff --git a/src/types.ts b/src/types.ts index 2925fd7..55e089b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,6 +36,13 @@ export type { Address, GenericSearchOptions, APISearchOptions, + ModelLanguage, + Morphology, +} from './utils/constants'; + +export { + MODEL_LANGUAGE, + MORPHOLOGY, } from './utils/constants'; // Account Adapter @@ -75,6 +82,16 @@ export type { Secret, } from './adapters/admin'; +// Automaton Adapter +export type { + Automata, + AutomatonStatus, + AutomatonParameterType, + AutomatonParameter, + AutomatonParameters, + AutomatedReadOutView, +} from './adapters/automaton'; + // Asset Adapter export type { Asset, @@ -88,6 +105,12 @@ export type { AppCredentials, } from './adapters/authentication'; +// Context Adapter +export type { + ModelTool, + ModelContext as ContextModelContext, +} from './adapters/context'; + // Channel Adapter export type { ChannelMessage, @@ -101,6 +124,15 @@ export type { ChatMessageReadOutView, } from './adapters/chat'; +// Dashboard Adapter +export type { + DashboardVersion, + PopType, + Pop, + Items as DashboardItems, + DashboardPreferenceReadOutView, +} from './adapters/dashboard'; + // Consensus Adapter export type { BarrierReadOutView, @@ -123,11 +155,26 @@ export type { Attachment, } from './adapters/email'; +// Encyclopedia Adapter +export type { + TranslatorFormat, + DocumentedParameter, + DocumentedEndpoint, + DocumentedResource, + KnownService, +} from './adapters/encyclopedia'; + // Episode Adapter export type { EpisodeReadOutView, } from './adapters/episode'; +// FIDO2 Adapter +export type { + PublicKeyCredentialRequestOptions, + PublicKeyCredentialCreationOptions, +} from './adapters/fido2'; + // Group Adapter export type { Group, @@ -146,6 +193,11 @@ export type { StripePaymentCreateInView, } from './adapters/group'; +// Information Adapter +export type { + InformationReadOutView, +} from './adapters/information'; + // Leaderboard Adapter export type { Leaderboard, @@ -153,6 +205,13 @@ export type { Tag, } from './adapters/leaderboard'; +// Notification Adapter +export type { + NotificationType, + NotificationPreferenceCreateInView, + NotificationPreferenceReadOutView, +} from './adapters/notification'; + // Presence Adapter export type { Presence, @@ -221,7 +280,6 @@ export type { NpmExternalDependency, PypiExternalDependency, ShellExternalDependency, - MORPHOLOGY, MetadataFirstPop, MetadataLastPop, MetadataAllPop, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index c40cfa9..e396535 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -383,6 +383,43 @@ export interface Permit { writeLock: keyof typeof ROLE; } +/** + * Supported model languages on the Epicenter platform. + */ +export const MODEL_LANGUAGE = { + EXCEL: 'EXCEL', + JAVA: 'JAVA', + JAVASCRIPT_20: 'JAVASCRIPT_20', + JAVASCRIPT_16: 'JAVASCRIPT_16', + JAVASCRIPT: 'JAVASCRIPT', + JULIA: 'JULIA', + POWERSIM: 'POWERSIM', + PYTHON_3: 'PYTHON_3', + PYTHON_2: 'PYTHON_2', + R: 'R', + SIMLANG: 'SIMLANG', + STELLA: 'STELLA', + TWINE: 'TWINE', + VENSIM: 'VENSIM', + JVM_VENSIM: 'JVM_VENSIM', + WASM: 'WASM', +} as const; + +export type ModelLanguage = (typeof MODEL_LANGUAGE)[keyof typeof MODEL_LANGUAGE]; + + +/** + * Model morphology defines how model instances are managed. + */ +export const MORPHOLOGY = { + MANY: 'MANY', + PROXY: 'PROXY', + SINGULAR: 'SINGULAR', +} as const; + +export type Morphology = (typeof MORPHOLOGY)[keyof typeof MORPHOLOGY]; + + export interface GenericScope { scopeBoundary: keyof typeof SCOPE_BOUNDARY; scopeKey: string; diff --git a/tests/automaton.spec.js b/tests/automaton.spec.js new file mode 100644 index 0000000..1115463 --- /dev/null +++ b/tests/automaton.spec.js @@ -0,0 +1,129 @@ +import { + it, + expect, + describe, + afterAll, + beforeAll, + beforeEach, +} from 'vitest'; +import { + ACCOUNT, + PROJECT, + SESSION, + GENERIC_OPTIONS, + createFetchMock, + testedMethods, + getAuthHeader, + authAdapter, + automatonAdapter, + config, + getFunctionKeys, +} from './common'; + +describe('automatonAdapter', () => { + let capturedRequests = []; + let mockSetup; + + config.accountShortName = ACCOUNT; + config.projectShortName = PROJECT; + + beforeAll(() => { + mockSetup = createFetchMock(); + capturedRequests = mockSetup.capturedRequests; + }); + + beforeEach(() => { + capturedRequests.length = 0; + authAdapter.setLocalSession(SESSION); + }); + + afterAll(() => { + mockSetup.restore(); + authAdapter.setLocalSession(undefined); + }); + + describe('automatonAdapter.start', () => { + const automata = 'ADD_USER_TO_ALL_GROUPS'; + const parameters = { + userId: { objectType: 'string', value: 'user123' }, + }; + + it('Should do a POST', async () => { + await automatonAdapter.start(automata, parameters); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await automatonAdapter.start(automata, parameters); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the automaton URL with automata action', async () => { + await automatonAdapter.start(automata, parameters); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toContain(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/automaton/${automata}`); + }); + + it('Should support generic URL options', async () => { + await automatonAdapter.start(automata, parameters, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toContain(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/automaton/${automata}`); + }); + + it('Should pass parameters in the request body', async () => { + await automatonAdapter.start(automata, parameters); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.options.body); + expect(body).toHaveProperty('parameters'); + expect(body.parameters).toEqual(parameters); + }); + + it('Should pass addNonce as a query parameter', async () => { + await automatonAdapter.start(automata, parameters, { addNonce: true }); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toContain('addNonce=true'); + }); + + testedMethods.add('start'); + }); + + describe('automatonAdapter.getStatus', () => { + const jobId = 12345; + + it('Should do a GET', async () => { + await automatonAdapter.getStatus(jobId); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await automatonAdapter.getStatus(jobId); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the automaton URL with jobId and project path segment', async () => { + await automatonAdapter.getStatus(jobId); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/automaton/${jobId}`); + }); + + it('Should support generic URL options', async () => { + await automatonAdapter.getStatus(jobId, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName } = GENERIC_OPTIONS; + const { projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/automaton/${jobId}`); + }); + + testedMethods.add('getStatus'); + }); + + it('Should not have any untested methods', () => { + const actualMethods = getFunctionKeys(automatonAdapter); + expect(actualMethods).toEqual(testedMethods); + }); +}); diff --git a/tests/common.js b/tests/common.js index 01b33a1..85b76a2 100644 --- a/tests/common.js +++ b/tests/common.js @@ -196,9 +196,15 @@ export const { accountAdapter, adminAdapter, assetAdapter, + automatonAdapter, + contextAdapter, + dashboardAdapter, chatAdapter, dailyAdapter, emailAdapter, + fido2Adapter, + forgeAdapter, + encyclopediaAdapter, episodeAdapter, matchmakerAdapter, presenceAdapter, @@ -208,6 +214,8 @@ export const { videoAdapter, vonageAdapter, cometdAdapter, + notificationAdapter, + informationAdapter, } = globalThis.epicenter || {}; export const testedMethods = new Set(); diff --git a/tests/context.spec.js b/tests/context.spec.js new file mode 100644 index 0000000..533892a --- /dev/null +++ b/tests/context.spec.js @@ -0,0 +1,184 @@ +import { + it, + expect, + describe, + afterAll, + beforeAll, + beforeEach, +} from 'vitest'; +import { + ACCOUNT, + PROJECT, + SESSION, + GENERIC_OPTIONS, + createFetchMock, + testedMethods, + getAuthHeader, + authAdapter, + contextAdapter, + config, + getFunctionKeys, +} from './common'; + +describe('contextAdapter', () => { + let capturedRequests = []; + let mockSetup; + + config.accountShortName = ACCOUNT; + config.projectShortName = PROJECT; + + beforeAll(() => { + mockSetup = createFetchMock(); + capturedRequests = mockSetup.capturedRequests; + }); + + beforeEach(() => { + capturedRequests.length = 0; + authAdapter.setLocalSession(SESSION); + }); + + afterAll(() => { + mockSetup.restore(); + authAdapter.setLocalSession(undefined); + }); + + describe('contextAdapter.get', () => { + const modelFile = 'model.vmf'; + + it('Should do a GET', async () => { + await contextAdapter.get(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await contextAdapter.get(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the context URL with modelFile', async () => { + await contextAdapter.get(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toContain(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/context/${modelFile}`); + }); + + it('Should support generic URL options', async () => { + await contextAdapter.get(modelFile, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toContain(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/context/${modelFile}`); + }); + + it('Should pass morphology as a query parameter', async () => { + await contextAdapter.get(modelFile, { morphology: 'SINGULAR' }); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toContain('morphology=SINGULAR'); + }); + + testedMethods.add('get'); + }); + + describe('contextAdapter.upgrade', () => { + const modelFile = 'model.vmf'; + + it('Should do a POST', async () => { + await contextAdapter.upgrade(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await contextAdapter.upgrade(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the context/upgrade URL with modelFile', async () => { + await contextAdapter.upgrade(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toContain(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/context/upgrade/${modelFile}`); + }); + + it('Should support generic URL options', async () => { + await contextAdapter.upgrade(modelFile, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toContain(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/context/upgrade/${modelFile}`); + }); + + it('Should pass morphology as a query parameter', async () => { + await contextAdapter.upgrade(modelFile, { morphology: 'MANY' }); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toContain('morphology=MANY'); + }); + + testedMethods.add('upgrade'); + }); + + describe('contextAdapter.verify', () => { + const modelFile = 'model.vmf'; + + it('Should do a POST', async () => { + await contextAdapter.verify(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await contextAdapter.verify(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the context/verify URL', async () => { + await contextAdapter.verify(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/context/verify`); + }); + + it('Should support generic URL options', async () => { + await contextAdapter.verify(modelFile, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/context/verify`); + }); + + it('Should pass modelFile in the request body', async () => { + await contextAdapter.verify(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.options.body); + expect(body).toHaveProperty('modelFile', modelFile); + }); + + it('Should pass optional parameters in the request body', async () => { + await contextAdapter.verify(modelFile, { + morphology: 'SINGULAR', + modelLanguage: 'VENSIM', + }); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.options.body); + expect(body).toHaveProperty('morphology', 'SINGULAR'); + expect(body).toHaveProperty('modelLanguage', 'VENSIM'); + }); + + it('Should pass executionContext in the request body', async () => { + const executionContext = { + version: 1, + tool: { objectType: 'vensim', sensitivityMode: true }, + }; + await contextAdapter.verify(modelFile, { executionContext }); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.options.body); + expect(body).toHaveProperty('executionContext'); + expect(body.executionContext).toEqual(executionContext); + }); + + testedMethods.add('verify'); + }); + + it('Should not have any untested methods', () => { + const actualMethods = getFunctionKeys(contextAdapter); + expect(actualMethods).toEqual(testedMethods); + }); +}); diff --git a/tests/dashboard.spec.js b/tests/dashboard.spec.js new file mode 100644 index 0000000..4f2c7e6 --- /dev/null +++ b/tests/dashboard.spec.js @@ -0,0 +1,324 @@ +import { + it, + expect, + describe, + afterAll, + beforeAll, + beforeEach, +} from 'vitest'; +import { + ACCOUNT, + PROJECT, + SESSION, + GENERIC_OPTIONS, + createFetchMock, + testedMethods, + getAuthHeader, + authAdapter, + dashboardAdapter, + config, + getFunctionKeys, +} from './common'; + +describe('dashboardAdapter', () => { + let capturedRequests = []; + let mockSetup; + + config.accountShortName = ACCOUNT; + config.projectShortName = PROJECT; + + beforeAll(() => { + mockSetup = createFetchMock(); + capturedRequests = mockSetup.capturedRequests; + }); + + beforeEach(() => { + capturedRequests.length = 0; + authAdapter.setLocalSession(SESSION); + }); + + afterAll(() => { + mockSetup.restore(); + authAdapter.setLocalSession(undefined); + }); + + describe('dashboardAdapter.get', () => { + const adminKey = 'my-admin-key'; + + it('Should do a GET', async () => { + await dashboardAdapter.get(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await dashboardAdapter.get(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the dashboard preference URL with version and adminKey', async () => { + await dashboardAdapter.get(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/dashboard/preference/V1/${adminKey}`); + }); + + it('Should support generic URL options', async () => { + await dashboardAdapter.get(adminKey, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/dashboard/preference/V1/${adminKey}`); + }); + + testedMethods.add('get'); + }); + + describe('dashboardAdapter.create', () => { + const adminKey = 'my-admin-key'; + + it('Should do a POST', async () => { + await dashboardAdapter.create(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await dashboardAdapter.create(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the dashboard preference URL with version and adminKey', async () => { + await dashboardAdapter.create(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/dashboard/preference/V1/${adminKey}`); + }); + + it('Should support generic URL options', async () => { + await dashboardAdapter.create(adminKey, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/dashboard/preference/V1/${adminKey}`); + }); + + it('Should pass items in the request body', async () => { + const items = { set: { theme: { dark: true } } }; + await dashboardAdapter.create(adminKey, { items }); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.options.body); + expect(body).toHaveProperty('items'); + expect(body.items).toEqual(items); + }); + + testedMethods.add('create'); + }); + + describe('dashboardAdapter.update', () => { + const adminKey = 'my-admin-key'; + const items = { set: { theme: { dark: true } } }; + + it('Should do a PUT', async () => { + await dashboardAdapter.update(adminKey, items); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('PUT'); + }); + + it('Should have authorization', async () => { + await dashboardAdapter.update(adminKey, items); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the dashboard preference URL with version and adminKey', async () => { + await dashboardAdapter.update(adminKey, items); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/dashboard/preference/V1/${adminKey}`); + }); + + it('Should support generic URL options', async () => { + await dashboardAdapter.update(adminKey, items, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/dashboard/preference/V1/${adminKey}`); + }); + + it('Should pass items in the request body', async () => { + await dashboardAdapter.update(adminKey, items); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.options.body); + expect(body).toEqual(items); + }); + + testedMethods.add('update'); + }); + + describe('dashboardAdapter.remove', () => { + const adminKey = 'my-admin-key'; + + it('Should do a DELETE', async () => { + await dashboardAdapter.remove(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('DELETE'); + }); + + it('Should have authorization', async () => { + await dashboardAdapter.remove(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the dashboard preference URL with version and adminKey', async () => { + await dashboardAdapter.remove(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/dashboard/preference/V1/${adminKey}`); + }); + + it('Should support generic URL options', async () => { + await dashboardAdapter.remove(adminKey, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/dashboard/preference/V1/${adminKey}`); + }); + + testedMethods.add('remove'); + }); + + describe('dashboardAdapter.getAll', () => { + it('Should do a GET', async () => { + await dashboardAdapter.getAll(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await dashboardAdapter.getAll(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the dashboard preference URL with version', async () => { + await dashboardAdapter.getAll(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/dashboard/preference/V1`); + }); + + it('Should support generic URL options', async () => { + await dashboardAdapter.getAll(GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/dashboard/preference/V1`); + }); + + testedMethods.add('getAll'); + }); + + describe('dashboardAdapter.createDefault', () => { + it('Should do a POST', async () => { + await dashboardAdapter.createDefault(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await dashboardAdapter.createDefault(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the dashboard preference URL with version', async () => { + await dashboardAdapter.createDefault(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/dashboard/preference/V1`); + }); + + it('Should support generic URL options', async () => { + await dashboardAdapter.createDefault(GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/dashboard/preference/V1`); + }); + + it('Should pass items in the request body', async () => { + const items = { set: { theme: { dark: true } } }; + await dashboardAdapter.createDefault({ items }); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.options.body); + expect(body).toHaveProperty('items'); + expect(body.items).toEqual(items); + }); + + testedMethods.add('createDefault'); + }); + + describe('dashboardAdapter.updateAll', () => { + const items = { set: { theme: { dark: true } } }; + + it('Should do a PUT', async () => { + await dashboardAdapter.updateAll(items); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('PUT'); + }); + + it('Should have authorization', async () => { + await dashboardAdapter.updateAll(items); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the dashboard preference URL with version', async () => { + await dashboardAdapter.updateAll(items); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/dashboard/preference/V1`); + }); + + it('Should support generic URL options', async () => { + await dashboardAdapter.updateAll(items, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/dashboard/preference/V1`); + }); + + it('Should pass items in the request body', async () => { + await dashboardAdapter.updateAll(items); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.options.body); + expect(body).toEqual(items); + }); + + testedMethods.add('updateAll'); + }); + + describe('dashboardAdapter.removeAll', () => { + it('Should do a DELETE', async () => { + await dashboardAdapter.removeAll(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('DELETE'); + }); + + it('Should have authorization', async () => { + await dashboardAdapter.removeAll(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the dashboard preference URL with version', async () => { + await dashboardAdapter.removeAll(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/dashboard/preference/V1`); + }); + + it('Should support generic URL options', async () => { + await dashboardAdapter.removeAll(GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/dashboard/preference/V1`); + }); + + testedMethods.add('removeAll'); + }); + + it('Should not have any untested methods', () => { + const actualMethods = getFunctionKeys(dashboardAdapter); + expect(actualMethods).toEqual(testedMethods); + }); +}); diff --git a/tests/encyclopedia.spec.js b/tests/encyclopedia.spec.js new file mode 100644 index 0000000..7d014ed --- /dev/null +++ b/tests/encyclopedia.spec.js @@ -0,0 +1,145 @@ +import { + it, + expect, + describe, + afterAll, + beforeAll, + beforeEach, +} from 'vitest'; +import { + ACCOUNT, + PROJECT, + SESSION, + GENERIC_OPTIONS, + createFetchMock, + testedMethods, + getAuthHeader, + authAdapter, + encyclopediaAdapter, + config, + getFunctionKeys, +} from './common'; + +describe('encyclopediaAdapter', () => { + let capturedRequests = []; + let mockSetup; + + config.accountShortName = ACCOUNT; + config.projectShortName = PROJECT; + + beforeAll(() => { + mockSetup = createFetchMock(); + capturedRequests = mockSetup.capturedRequests; + }); + + beforeEach(() => { + capturedRequests.length = 0; + authAdapter.setLocalSession(SESSION); + }); + + afterAll(() => { + mockSetup.restore(); + authAdapter.setLocalSession(undefined); + }); + + describe('encyclopediaAdapter.list', () => { + const version = 1; + + it('Should do a GET', async () => { + await encyclopediaAdapter.list(version); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await encyclopediaAdapter.list(version); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the encyclopedia URL with version', async () => { + await encyclopediaAdapter.list(version); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/encyclopedia/v${version}`); + }); + + it('Should support generic URL options', async () => { + await encyclopediaAdapter.list(version, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/encyclopedia/v${version}`); + }); + + testedMethods.add('list'); + }); + + describe('encyclopediaAdapter.get', () => { + const version = 1; + const api = 'run'; + + it('Should do a GET', async () => { + await encyclopediaAdapter.get(version, api); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await encyclopediaAdapter.get(version, api); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the encyclopedia URL with version and api', async () => { + await encyclopediaAdapter.get(version, api); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/encyclopedia/v${version}/${api}`); + }); + + it('Should support generic URL options', async () => { + await encyclopediaAdapter.get(version, api, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/encyclopedia/v${version}/${api}`); + }); + + testedMethods.add('get'); + }); + + describe('encyclopediaAdapter.translate', () => { + const version = 1; + const api = 'run'; + const translator = 'OPENAPI'; + + it('Should do a GET', async () => { + await encyclopediaAdapter.translate(version, api, translator); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await encyclopediaAdapter.translate(version, api, translator); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the encyclopedia URL with translator, version, and api', async () => { + await encyclopediaAdapter.translate(version, api, translator); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/encyclopedia/as/${translator}/v${version}/${api}`); + }); + + it('Should support generic URL options', async () => { + await encyclopediaAdapter.translate(version, api, translator, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/encyclopedia/as/${translator}/v${version}/${api}`); + }); + + testedMethods.add('translate'); + }); + + it('Should not have any untested methods', () => { + const actualMethods = getFunctionKeys(encyclopediaAdapter); + expect(actualMethods).toEqual(testedMethods); + }); +}); diff --git a/tests/fido2.spec.js b/tests/fido2.spec.js new file mode 100644 index 0000000..33f0cde --- /dev/null +++ b/tests/fido2.spec.js @@ -0,0 +1,183 @@ +import { + it, + expect, + describe, + afterAll, + beforeAll, + beforeEach, +} from 'vitest'; +import { + ACCOUNT, + PROJECT, + SESSION, + GENERIC_OPTIONS, + createFetchMock, + testedMethods, + getAuthHeader, + authAdapter, + fido2Adapter, + config, + getFunctionKeys, +} from './common'; + +describe('fido2Adapter', () => { + let capturedRequests = []; + let mockSetup; + + config.accountShortName = ACCOUNT; + config.projectShortName = PROJECT; + + beforeAll(() => { + mockSetup = createFetchMock(); + capturedRequests = mockSetup.capturedRequests; + }); + + beforeEach(() => { + capturedRequests.length = 0; + authAdapter.setLocalSession(SESSION); + }); + + afterAll(() => { + mockSetup.restore(); + authAdapter.setLocalSession(undefined); + }); + + describe('fido2Adapter.getRequestOptions', () => { + it('Should do a GET', async () => { + await fido2Adapter.getRequestOptions(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await fido2Adapter.getRequestOptions(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the fido2/options/request URL', async () => { + await fido2Adapter.getRequestOptions(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/fido2/options/request`); + }); + + it('Should support generic URL options', async () => { + await fido2Adapter.getRequestOptions(GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/fido2/options/request`); + }); + + testedMethods.add('getRequestOptions'); + }); + + describe('fido2Adapter.getCreateOptions', () => { + it('Should do a GET', async () => { + await fido2Adapter.getCreateOptions(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await fido2Adapter.getCreateOptions(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the fido2/options/create URL', async () => { + await fido2Adapter.getCreateOptions(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/fido2/options/create`); + }); + + it('Should support generic URL options', async () => { + await fido2Adapter.getCreateOptions(GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/fido2/options/create`); + }); + + testedMethods.add('getCreateOptions'); + }); + + describe('fido2Adapter.register', () => { + const credential = { id: 'cred-id', response: { attestationObject: 'abc' } }; + + it('Should do a POST', async () => { + await fido2Adapter.register(credential); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await fido2Adapter.register(credential); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the fido2/register URL', async () => { + await fido2Adapter.register(credential); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/fido2/register`); + }); + + it('Should send the credential in the body', async () => { + await fido2Adapter.register(credential); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.requestBody); + expect(body.id).toBe(credential.id); + }); + + it('Should support generic URL options', async () => { + await fido2Adapter.register(credential, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/fido2/register`); + }); + + testedMethods.add('register'); + }); + + describe('fido2Adapter.verify', () => { + const assertion = { id: 'cred-id', response: { authenticatorData: 'xyz' } }; + + it('Should do a POST', async () => { + await fido2Adapter.verify(assertion); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await fido2Adapter.verify(assertion); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the fido2/verify URL', async () => { + await fido2Adapter.verify(assertion); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/fido2/verify`); + }); + + it('Should send the assertion in the body', async () => { + await fido2Adapter.verify(assertion); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.requestBody); + expect(body.id).toBe(assertion.id); + }); + + it('Should support generic URL options', async () => { + await fido2Adapter.verify(assertion, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/fido2/verify`); + }); + + testedMethods.add('verify'); + }); + + it('Should not have any untested methods', () => { + const actualMethods = getFunctionKeys(fido2Adapter); + expect(actualMethods).toEqual(testedMethods); + }); +}); diff --git a/tests/forge.spec.js b/tests/forge.spec.js new file mode 100644 index 0000000..8abb172 --- /dev/null +++ b/tests/forge.spec.js @@ -0,0 +1,308 @@ +import { + it, + expect, + describe, + afterAll, + beforeAll, + beforeEach, +} from 'vitest'; +import { + ACCOUNT, + PROJECT, + SESSION, + GENERIC_OPTIONS, + createFetchMock, + testedMethods, + getAuthHeader, + authAdapter, + forgeAdapter, + config, + getFunctionKeys, +} from './common'; + +describe('forgeAdapter', () => { + let capturedRequests = []; + let mockSetup; + + config.accountShortName = ACCOUNT; + config.projectShortName = PROJECT; + + beforeAll(() => { + mockSetup = createFetchMock(); + capturedRequests = mockSetup.capturedRequests; + }); + + beforeEach(() => { + capturedRequests.length = 0; + authAdapter.setLocalSession(SESSION); + }); + + afterAll(() => { + mockSetup.restore(); + authAdapter.setLocalSession(undefined); + }); + + describe('forgeAdapter.ready', () => { + const modelLanguage = 'PYTHON_3'; + + it('Should do a POST', async () => { + await forgeAdapter.ready(modelLanguage); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await forgeAdapter.ready(modelLanguage); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the forge/ready URL', async () => { + await forgeAdapter.ready(modelLanguage); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/forge/ready`); + }); + + it('Should send modelLanguage in the body', async () => { + await forgeAdapter.ready(modelLanguage); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.requestBody); + expect(body.modelLanguage).toBe(modelLanguage); + }); + + it('Should support generic URL options', async () => { + await forgeAdapter.ready(modelLanguage, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/forge/ready`); + }); + + testedMethods.add('ready'); + }); + + describe('forgeAdapter.rebuild', () => { + it('Should do a POST', async () => { + await forgeAdapter.rebuild(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await forgeAdapter.rebuild(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the forge/rebuild URL', async () => { + await forgeAdapter.rebuild(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/forge/rebuild`); + }); + + it('Should pass optional parameters to the request body', async () => { + await forgeAdapter.rebuild({ noCache: true, cleanUp: true }); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.requestBody); + expect(body.noCache).toBe(true); + expect(body.cleanUp).toBe(true); + }); + + it('Should support generic URL options', async () => { + await forgeAdapter.rebuild(GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/forge/rebuild`); + }); + + testedMethods.add('rebuild'); + }); + + describe('forgeAdapter.setDefault', () => { + const modelLanguage = 'JAVASCRIPT'; + + it('Should do a POST', async () => { + await forgeAdapter.setDefault(modelLanguage); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await forgeAdapter.setDefault(modelLanguage); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the forge/default URL', async () => { + await forgeAdapter.setDefault(modelLanguage); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/forge/default`); + }); + + it('Should send modelLanguage in the body', async () => { + await forgeAdapter.setDefault(modelLanguage); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.requestBody); + expect(body.modelLanguage).toBe(modelLanguage); + }); + + it('Should pass optional parameters to the request body', async () => { + await forgeAdapter.setDefault(modelLanguage, { + noCache: true, + modelMorphology: 'SINGULAR', + workerImage: 'custom-image', + }); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.requestBody); + expect(body.noCache).toBe(true); + expect(body.modelMorphology).toBe('SINGULAR'); + expect(body.workerImage).toBe('custom-image'); + }); + + it('Should support generic URL options', async () => { + await forgeAdapter.setDefault(modelLanguage, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/forge/default`); + }); + + testedMethods.add('setDefault'); + }); + + describe('forgeAdapter.setDefaultAll', () => { + it('Should do a POST', async () => { + await forgeAdapter.setDefaultAll(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await forgeAdapter.setDefaultAll(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the forge/default/all URL', async () => { + await forgeAdapter.setDefaultAll(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/forge/default/all`); + }); + + it('Should pass optional parameters to the request body', async () => { + await forgeAdapter.setDefaultAll({ noCache: true, cleanUp: false }); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.requestBody); + expect(body.noCache).toBe(true); + expect(body.cleanUp).toBe(false); + }); + + it('Should support generic URL options', async () => { + await forgeAdapter.setDefaultAll(GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/forge/default/all`); + }); + + testedMethods.add('setDefaultAll'); + }); + + describe('forgeAdapter.setDefaults', () => { + const modelLanguages = ['PYTHON_3', 'JAVASCRIPT']; + + it('Should do a POST', async () => { + await forgeAdapter.setDefaults(modelLanguages); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await forgeAdapter.setDefaults(modelLanguages); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the forge/defaults URL', async () => { + await forgeAdapter.setDefaults(modelLanguages); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/forge/defaults`); + }); + + it('Should send modelLanguages in the body', async () => { + await forgeAdapter.setDefaults(modelLanguages); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.requestBody); + expect(body.modelLanguages).toEqual(modelLanguages); + }); + + it('Should pass optional parameters to the request body', async () => { + await forgeAdapter.setDefaults(modelLanguages, { modelMorphology: 'MANY' }); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.requestBody); + expect(body.modelMorphology).toBe('MANY'); + }); + + it('Should support generic URL options', async () => { + await forgeAdapter.setDefaults(modelLanguages, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/forge/defaults`); + }); + + testedMethods.add('setDefaults'); + }); + + describe('forgeAdapter.build', () => { + const modelFile = 'model.py'; + + it('Should do a POST', async () => { + await forgeAdapter.build(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await forgeAdapter.build(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the forge/build URL', async () => { + await forgeAdapter.build(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/forge/build`); + }); + + it('Should send modelFile in the body', async () => { + await forgeAdapter.build(modelFile); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.requestBody); + expect(body.modelFile).toBe(modelFile); + }); + + it('Should pass optional parameters to the request body', async () => { + await forgeAdapter.build(modelFile, { + noCache: true, + modelLanguage: 'PYTHON_3', + modelMorphology: 'SINGULAR', + }); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.requestBody); + expect(body.noCache).toBe(true); + expect(body.modelLanguage).toBe('PYTHON_3'); + expect(body.modelMorphology).toBe('SINGULAR'); + }); + + it('Should support generic URL options', async () => { + await forgeAdapter.build(modelFile, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/forge/build`); + }); + + testedMethods.add('build'); + }); + + it('Should not have any untested methods', () => { + const actualMethods = getFunctionKeys(forgeAdapter); + expect(actualMethods).toEqual(testedMethods); + }); +}); diff --git a/tests/information.spec.js b/tests/information.spec.js new file mode 100644 index 0000000..0ab78a2 --- /dev/null +++ b/tests/information.spec.js @@ -0,0 +1,78 @@ +import { + it, + expect, + describe, + afterAll, + beforeAll, + beforeEach, +} from 'vitest'; +import { + ACCOUNT, + PROJECT, + SESSION, + GENERIC_OPTIONS, + createFetchMock, + testedMethods, + getAuthHeader, + authAdapter, + config, + getFunctionKeys, + informationAdapter, +} from './common'; + +describe('informationAdapter', () => { + let capturedRequests = []; + let mockSetup; + + config.accountShortName = ACCOUNT; + config.projectShortName = PROJECT; + + beforeAll(() => { + mockSetup = createFetchMock(); + capturedRequests = mockSetup.capturedRequests; + }); + + beforeEach(() => { + capturedRequests.length = 0; + authAdapter.setLocalSession(SESSION); + }); + + afterAll(() => { + mockSetup.restore(); + authAdapter.setLocalSession(undefined); + }); + + describe('informationAdapter.get', () => { + it('Should do a GET', async () => { + await informationAdapter.get(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await informationAdapter.get(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the information URL', async () => { + await informationAdapter.get(); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/information`); + }); + + it('Should support generic URL options', async () => { + await informationAdapter.get(GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/information`); + }); + + testedMethods.add('get'); + }); + + it('Should not have any untested methods', () => { + const actualMethods = getFunctionKeys(informationAdapter); + expect(actualMethods).toEqual(testedMethods); + }); +}); diff --git a/tests/notification.spec.js b/tests/notification.spec.js new file mode 100644 index 0000000..1f78da9 --- /dev/null +++ b/tests/notification.spec.js @@ -0,0 +1,187 @@ +import { + it, + expect, + describe, + afterAll, + beforeAll, + beforeEach, +} from 'vitest'; +import { + ACCOUNT, + PROJECT, + SESSION, + GENERIC_OPTIONS, + createFetchMock, + testedMethods, + getAuthHeader, + authAdapter, + config, + getFunctionKeys, + notificationAdapter, +} from './common'; + +describe('notificationAdapter', () => { + let capturedRequests = []; + let mockSetup; + + config.accountShortName = ACCOUNT; + config.projectShortName = PROJECT; + + beforeAll(() => { + mockSetup = createFetchMock(); + capturedRequests = mockSetup.capturedRequests; + }); + + beforeEach(() => { + capturedRequests.length = 0; + authAdapter.setLocalSession(SESSION); + }); + + afterAll(() => { + mockSetup.restore(); + authAdapter.setLocalSession(undefined); + }); + + describe('notificationAdapter.setPreference', () => { + const preference = { + notificationType: 'MARKETING', + adminKey: 'my-admin-key', + notify: false, + }; + + it('Should do a POST', async () => { + await notificationAdapter.setPreference(preference); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('POST'); + }); + + it('Should have authorization', async () => { + await notificationAdapter.setPreference(preference); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the notification/preference URL', async () => { + await notificationAdapter.setPreference(preference); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/notification/preference`); + }); + + it('Should support generic URL options', async () => { + await notificationAdapter.setPreference(preference, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/notification/preference`); + }); + + it('Should pass preference to the request body', async () => { + await notificationAdapter.setPreference(preference); + const req = capturedRequests[capturedRequests.length - 1]; + const body = JSON.parse(req.options.body); + expect(body).toHaveProperty('notificationType', 'MARKETING'); + expect(body).toHaveProperty('adminKey', 'my-admin-key'); + expect(body).toHaveProperty('notify', false); + }); + + testedMethods.add('setPreference'); + }); + + describe('notificationAdapter.getPreferences', () => { + const adminKey = 'my-admin-key'; + + it('Should do a GET', async () => { + await notificationAdapter.getPreferences(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await notificationAdapter.getPreferences(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the notification/preference URL with adminKey', async () => { + await notificationAdapter.getPreferences(adminKey); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/notification/preference/${adminKey}`); + }); + + it('Should support generic URL options', async () => { + await notificationAdapter.getPreferences(adminKey, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/notification/preference/${adminKey}`); + }); + + testedMethods.add('getPreferences'); + }); + + describe('notificationAdapter.optOut', () => { + const optOutToken = 'my-opt-out-token'; + + it('Should do a GET', async () => { + await notificationAdapter.optOut(optOutToken); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await notificationAdapter.optOut(optOutToken); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the notification/optout URL with token', async () => { + await notificationAdapter.optOut(optOutToken); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/notification/optout/${optOutToken}`); + }); + + it('Should support generic URL options', async () => { + await notificationAdapter.optOut(optOutToken, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/notification/optout/${optOutToken}`); + }); + + testedMethods.add('optOut'); + }); + + describe('notificationAdapter.optOutByType', () => { + const notificationType = 'MARKETING'; + const optOutToken = 'my-opt-out-token'; + + it('Should do a GET', async () => { + await notificationAdapter.optOutByType(notificationType, optOutToken); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.options.method.toUpperCase()).toBe('GET'); + }); + + it('Should have authorization', async () => { + await notificationAdapter.optOutByType(notificationType, optOutToken); + const req = capturedRequests[capturedRequests.length - 1]; + expect(getAuthHeader(req.requestHeaders)).toBe(`Bearer ${SESSION.token}`); + }); + + it('Should use the notification/optout URL with type and token', async () => { + await notificationAdapter.optOutByType(notificationType, optOutToken); + const req = capturedRequests[capturedRequests.length - 1]; + expect(req.url).toBe(`https://${config.apiHost}/api/v${config.apiVersion}/${config.accountShortName}/${config.projectShortName}/notification/optout/${notificationType}/${optOutToken}`); + }); + + it('Should support generic URL options', async () => { + await notificationAdapter.optOutByType(notificationType, optOutToken, GENERIC_OPTIONS); + const req = capturedRequests[capturedRequests.length - 1]; + const { server, accountShortName, projectShortName } = GENERIC_OPTIONS; + expect(req.url).toBe(`${server}/api/v${config.apiVersion}/${accountShortName}/${projectShortName}/notification/optout/${notificationType}/${optOutToken}`); + }); + + testedMethods.add('optOutByType'); + }); + + it('Should not have any untested methods', () => { + const actualMethods = getFunctionKeys(notificationAdapter); + expect(actualMethods).toEqual(testedMethods); + }); +}); diff --git a/vitest.config.js b/vitest.config.js index e052bcf..03c93df 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -6,6 +6,11 @@ export default defineConfig({ globals: true, environment: 'jsdom', setupFiles: ['./tests/setup.js'], + server: { + deps: { + inline: ['regenerator-runtime', '@babel/runtime'], + }, + }, include: ['tests/**/*.test.{js,ts}', 'tests/**/*.spec.{js,ts}'], coverage: { provider: 'v8',