Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2807e77
chore: 🤖 import explicit mirage references
hashicc Apr 28, 2025
82ee865
chore: 🤖 add mirage setupMirage helper
hashicc Apr 28, 2025
538ae64
chore: 🤖 working example with application adapter test
hashicc Apr 28, 2025
b72c562
chore: 🤖 use internal setupMirage test helper from api addon
hashicc Nov 14, 2025
c5f3580
chore: 🤖 conditionally load mirage
hashicc Nov 14, 2025
853a081
chore: 🤖 filter mirage folder inclusion in production builds
hashicc Nov 14, 2025
d582e67
chore: 🤖 drop ember-mirage and use our own test helpers
hashicc Nov 20, 2025
ffe2a5a
chore: 🤖 collapse reference to import declaration
hashicc Nov 20, 2025
797b32e
docs: ✏️ add comment better explaining the mirage test env
hashicc Nov 20, 2025
7a30c84
chore: 🤖 update configuration and manage mirage-related deps
hashicc Nov 21, 2025
f86586e
fix: 🐛 restore ipc mock and add missing checkOS handler
hashicc Nov 21, 2025
57d8c50
chore: 🤖 add comment explaining omitting mirage deps in build
hashicc Nov 21, 2025
745eb5b
chore: 🤖 allow mirage.enabled to include deps and handlers
hashicc Nov 24, 2025
e641cdf
chore: 🤖 re-enable mirage for core dummy app
hashicc Nov 24, 2025
7ef0eed
Add missing copyright headers
hashicc Nov 24, 2025
aa1d435
chore: 🤖 use embroider macros to conditionally start mirage
hashicc Nov 24, 2025
3ccf386
docs: ✏️ update api readme for mirage changes
hashicc Nov 24, 2025
86e098f
chore: 🤖 replace remaining ember-cli-mirage references
hashicc Nov 24, 2025
1fbc493
chore: 🤖 add mirage to test builds
hashicc Nov 24, 2025
20f81e8
fix: 🐛 ensure that auto mirage start doesn't happen in test env
hashicc Nov 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions addons/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,45 @@ This addon contains the API data access layer for Boundary.
Add this addon to an Ember application's `devDependencies` as:
`"api": "workspace:*"`, for applications included in this monorepo.

Since this addon also includes Mirage mocks, be sure to install
`ember-cli-mirage` and add the following config to your UI project:
This addon also includes Mirage mocks. To include the `miragejs` dependency and this addon's mirage handlers configure the consuming app's `config/environment.js`

```js
'ember-cli-mirage': {
directory: '../../addons/api/mirage'
mirage: {
enabled: true
}
```

To have mirage start and intercept requests when the application starts:
1. Add the `@embroider/macros` dependency to your application
2. Configure `@embroider/macros` with `startMirageWithApp` based on app's config within `ember-cli-build.js`:

```js
// ember-cli-build.js
module.exports = async function (defaults) {
// load the app's config
const { EMBER_ENV } = process.env;
var config = require('./config/environment')(EMBER_ENV);

const app = new EmberApp(defaults, {
'@embroider/macros': {
setOwnConfig: {
startMirageWithApp: config.mirage?.enabled ?? false
},
},
});
}
```

3. Finally, use the `@embroider/macros` config value for `startMirageWithApp` in `app/app.js` to conditionally start mirage:

```js
import { macroCondition, importSync, getOwnConfig, isTesting } from '@embroider/macros';

if (macroCondition(getOwnConfig().startMirageWithApp && !isTesting())) {
const startServer = importSync('api/mirage/config').default;
startServer({});
}
```
## Installation

See monorepo README for installation instructions.
Expand Down
33 changes: 33 additions & 0 deletions addons/api/addon-test-support/helpers/mirage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import { settled } from '@ember/test-helpers';
import startMirage from 'api/mirage/config';

export function setupMirage(hooks) {
hooks.beforeEach(function () {
if (!this.owner) {
throw new Error(
'Must call one of the ember-qunit setupTest() / setupRenderingTest() / setupApplicationTest() first',
);
}

// the environment property here is configuration to the mirage server:
// https://github.com/miragejs/miragejs/blob/7ff4f3f6fe56bf0cb1648f5af3f5210fcb07e20b/types/index.d.ts#L383
// It is not related to ember's build environment. In this case for mirage the "test" environment does
// not load the default scenario:
// https://github.com/miragejs/miragejs/blob/7ff4f3f6fe56bf0cb1648f5af3f5210fcb07e20b/lib/server.js#L302
this.server = startMirage({ environment: 'test' });
});

hooks.afterEach(function () {
return settled().then(() => {
if (this.server) {
this.server.shutdown();
delete this.server;
}
});
});
}
173 changes: 163 additions & 10 deletions addons/api/mirage/config.js → addons/api/addon/mirage/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@
* SPDX-License-Identifier: BUSL-1.1
*/

import {
discoverEmberDataModels,
applyEmberDataSerializers,
} from 'ember-cli-mirage';
import { createServer, Response } from 'miragejs';
import environmentConfig from '../config/environment';
import { authHandler, deauthHandler } from './route-handlers/auth';
import { targetHandler } from './route-handlers/target';
import { pickRandomStatusString } from './factories/session';
Expand All @@ -17,19 +12,177 @@ import makeBooleanFilter from './helpers/bexpr-filter';
import { faker } from '@faker-js/faker';
import { asciicasts } from './data/asciicasts';
import { TYPE_WORKER_PKI } from 'api/models/worker';
import environmentConfig from 'ember-get-config';

// mirage models (alphabetical)
import accountModel from './models/account';
Copy link
Collaborator Author

@hashicc hashicc Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of the build magic ember-cli-mirage provided was auto-discovery and inclusion of various models, factories, serializers based on the folder structure. This now has to be done manually. When we move to vite we should be able to use import.meta.glob and specify all the models in one glob pattern but until then they'll have to be done manually, one-by-one.

import aliasModel from './models/alias';
import authMethodModel from './models/auth-method';
import baseModel from './models/base';
import channelRecordingModel from './models/channel-recording';
import connectionRecordingModel from './models/connection-recording';
import credentialLibraryModel from './models/credential-library';
import credentialStoreModel from './models/credential-store';
import credentialModel from './models/credential';
import groupModel from './models/group';
import hostCatalogModel from './models/host-catalog';
import hostSetModel from './models/host-set';
import hostModel from './models/host';
import managedGroupModel from './models/managed-group';
import policyModel from './models/policy';
import roleModel from './models/role';
import scopeModel from './models/scope';
import sessionRecordingModel from './models/session-recording';
import sessionModel from './models/session';
import storageBucketModel from './models/storage-bucket';
import targetModel from './models/target';
import userModel from './models/user';
import workerModel from './models/worker';

// mirage serializers (alphabetical)
import accountSerializer from './serializers/account';
import aliasSerializer from './serializers/alias';
import applicationSerializer from './serializers/application';
import authMethodSerializer from './serializers/auth-method';
import channelRecordingSerializer from './serializers/channel-recording';
import connectionRecordingSerializer from './serializers/connection-recording';
import credentialLibrarySerializer from './serializers/credential-library';
import credentialStoreSerializer from './serializers/credential-store';
import credentialSerializer from './serializers/credential';
import groupSerializer from './serializers/group';
import hostCatalogSerializer from './serializers/host-catalog';
import hostSetSerializer from './serializers/host-set';
import hostSerializer from './serializers/host';
import managedGroupSerializer from './serializers/managed-group';
import policySerializer from './serializers/policy';
import roleSerializer from './serializers/role';
import scopeSerializer from './serializers/scope';
import sessionRecordingSerializer from './serializers/session-recording';
import sessionSerializer from './serializers/session';
import storageBucketSerializer from './serializers/storage-bucket';
import targetSerializer from './serializers/target';
import userSerializer from './serializers/user';
import workerSerializer from './serializers/worker';

// mirage scenarios (alphabetical)
import defaultScenario from './scenarios/default';
import ipcScenario from './scenarios/ipc';

// mirage factories (alphabetical)
import accountFactory from './factories/account';
import aliasFactory from './factories/alias';
import authMethodFactory from './factories/auth-method';
import channelRecordingFactory from './factories/channel-recording';
import connectionRecordingFactory from './factories/connection-recording';
import credentialLibraryFactory from './factories/credential-library';
import credentialStoreFactory from './factories/credential-store';
import credentialFactory from './factories/credential';
import groupFactory from './factories/group';
import hostCatalogFactory from './factories/host-catalog';
import hostSetFactory from './factories/host-set';
import hostFactory from './factories/host';
import managedGroupFactory from './factories/managed-group';
import policyFactory from './factories/policy';
import roleFactory from './factories/role';
import scopeFactory from './factories/scope';
import sessionRecordingFactory from './factories/session-recording';
import storageBucketFactory from './factories/storage-bucket';
import sessionFactory from './factories/session';
import targetFactory from './factories/target';
import userFactory from './factories/user';
import workerFactory from './factories/worker';

const isTesting = environmentConfig.environment === 'test';

// Main function
// More info about server configuration https://www.ember-cli-mirage.com/docs/advanced/server-configuration
// More info about server configuration:
// https://github.com/miragejs/miragejs/blob/7ff4f3f6fe56bf0cb1648f5af3f5210fcb07e20b/types/index.d.ts#L375-L404
export default function (mirageConfig) {
let finalConfig = {
...mirageConfig,

scenarios: {
default: defaultScenario,
ipcScenario: ipcScenario,
},

factories: {
account: accountFactory,
alias: aliasFactory,
authMethod: authMethodFactory,
channelRecording: channelRecordingFactory,
connectionRecording: connectionRecordingFactory,
credentialLibrary: credentialLibraryFactory,
credentialStore: credentialStoreFactory,
credential: credentialFactory,
group: groupFactory,
hostCatalog: hostCatalogFactory,
hostSet: hostSetFactory,
host: hostFactory,
managedGroup: managedGroupFactory,
policy: policyFactory,
role: roleFactory,
scope: scopeFactory,
sessionRecording: sessionRecordingFactory,
session: sessionFactory,
storageBucket: storageBucketFactory,
target: targetFactory,
user: userFactory,
worker: workerFactory,
},

models: {
...discoverEmberDataModels(mirageConfig.store),
...mirageConfig.models,
account: accountModel,
alias: aliasModel,
authMethod: authMethodModel,
base: baseModel,
channelRecording: channelRecordingModel,
connectionRecording: connectionRecordingModel,
credentialLibrary: credentialLibraryModel,
credentialStore: credentialStoreModel,
credential: credentialModel,
group: groupModel,
hostCatalog: hostCatalogModel,
hostSet: hostSetModel,
host: hostModel,
managedGroup: managedGroupModel,
policy: policyModel,
role: roleModel,
scope: scopeModel,
sessionRecording: sessionRecordingModel,
session: sessionModel,
storageBucket: storageBucketModel,
target: targetModel,
user: userModel,
worker: workerModel,
},

serializers: {
account: accountSerializer,
alias: aliasSerializer,
application: applicationSerializer,
authMethod: authMethodSerializer,
channelRecording: channelRecordingSerializer,
connectionRecording: connectionRecordingSerializer,
credentialLibrary: credentialLibrarySerializer,
credentialStore: credentialStoreSerializer,
credential: credentialSerializer,
group: groupSerializer,
hostCatalog: hostCatalogSerializer,
hostSet: hostSetSerializer,
host: hostSerializer,
managedGroup: managedGroupSerializer,
policy: policySerializer,
role: roleSerializer,
scope: scopeSerializer,
sessionRecording: sessionRecordingSerializer,
session: sessionSerializer,
storageBucket: storageBucketSerializer,
target: targetSerializer,
user: userSerializer,
worker: workerSerializer,
},
serializers: applyEmberDataSerializers(mirageConfig.serializers),

routes,
};
return createServer(finalConfig);
Expand Down Expand Up @@ -58,7 +211,7 @@ function routes() {
});

// make this `/api`, for example, if your API is namespaced
this.namespace = environmentConfig.api.namespace;
this.namespace = environmentConfig.api?.namespace;
// delay for each request, automatically set to 0 during testing
this.timing = 1;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ export default function initializeMockIPC(server, config) {

clusterUrl = null;

checkOS() {
return {
isLinux: false,
isMac: true,
isWindows: false,
};
}

getClusterUrl() {
return this.clusterUrl;
}
Expand Down Expand Up @@ -273,7 +281,7 @@ export default function initializeMockIPC(server, config) {
* We mock certain functions even in electron (e.g. hasMacOSChrome) when running
* locally which will force a certain appearance regardless of platform
*/
if (config['ember-cli-mirage'].enabled && !isTesting) {
if (config.mirage?.enabled && !isTesting) {
const mockIPC = new MockIPC();

window.addEventListener('message', async function (event) {
Expand Down
32 changes: 31 additions & 1 deletion addons/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,36 @@ module.exports = {
},
},

included() {
this._super.included.apply(this, arguments);
const includeMirage = this._includeMirageInBuild();

// these are dependencies used our mirage code within the
// api addon and should not be included in production builds,
// after this addon has been migrated to a v2 addon this can
// be removed as the addon's dependencies will be statically
// analyzable
if (!includeMirage) {
// exclude mirage and dependencies from this addon that are used by mirage
this.options.autoImport.exclude.push(
'miragejs',
'sinon',
'@faker-js/faker',
'js-bexpr',
);
}
},

treeForAddon() {
const includeMirage = this._includeMirageInBuild();

// Exclude anything in the workers folder from being bundled in the final
// build as we're manually bundling the files ourselves below.
const tree = this._super.treeForAddon.apply(this, arguments);
return funnel(tree, {
exclude: ['api/workers/**/*'],
exclude: ['api/workers/**/*', !includeMirage && 'api/mirage/**/*'].filter(
Boolean,
),
});
},

Expand Down Expand Up @@ -70,4 +94,10 @@ module.exports = {

return merge(trees);
},

_includeMirageInBuild() {
const env = this.parent.app?.env ?? 'production';
const config = this.project.config(env);
return Boolean(config.mirage?.enabled);
},
};
Loading
Loading