Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/backend/src/CoreModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,9 @@ const install = async ({ context, services, app, useapi, modapi }) => {

const { FileCacheService } = require('./services/file-cache/FileCacheService');
services.registerService('file-cache', FileCacheService);

const { TestService } = require('./services/TestService');
services.registerService('__test', TestService);
};

const install_legacy = async ({ services }) => {
Expand Down
76 changes: 76 additions & 0 deletions src/backend/src/services/BaseService.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,82 @@ class BaseService extends concepts.Service {
|| this.constructor[`__on_${id}`]?.bind?.(this.constructor)
|| NOOP;
}

/**
* Sets the service mode, enabling method overriding with prefixed methods.
* This allows services to be "modal" - switching between different behavior modes
* (e.g., test mode, debug mode, etc.) by overriding methods with prefixed versions.
*
* @param {Object|null} mode - Mode configuration object or null to restore normal mode
* @param {string} [mode.override_prefix] - Prefix for methods that should override their unprefixed counterparts
* (e.g., '__test_' will make __test_methodName override methodName)
* @returns {void}
*
* @example
* // Enable test mode
* service.setServiceMode({ override_prefix: '__test_' });
*
* // Restore normal mode
* service.setServiceMode(null);
*/
setServiceMode (mode) {
// Restore previous mode if switching modes
if ( this._serviceModeState ) {
this._restoreServiceMode();
}

// If mode is null or empty, just restore and return
if ( ! mode || ! mode.override_prefix ) {
this._serviceModeState = null;
return;
}

// Store mode state
this._serviceModeState = {
override_prefix: mode.override_prefix,
methodOverrides: {},
};

// Discover and apply method overrides
const checkSources = [
Object.getPrototypeOf(this),
this
];

for ( const source of checkSources ) {
for ( const key of Object.getOwnPropertyNames(source) ) {
if ( key.startsWith(mode.override_prefix) && typeof this[key] === 'function' ) {
const originalMethodName = key.slice(mode.override_prefix.length);
if ( typeof this[originalMethodName] === 'function' ) {
// Store original method for restoration (only if not already stored)
if ( ! this._serviceModeState.methodOverrides[originalMethodName] ) {
this._serviceModeState.methodOverrides[originalMethodName] = this[originalMethodName];
}
// Override with prefixed method
this[originalMethodName] = this[key].bind(this);
}
}
}
}
}

/**
* Restores the service to normal mode by reverting all method overrides.
* @private
* @returns {void}
*/
_restoreServiceMode () {
if ( ! this._serviceModeState || ! this._serviceModeState.methodOverrides ) {
return;
}

// Restore original methods
for ( const [methodName, originalMethod] of Object.entries(this._serviceModeState.methodOverrides) ) {
this[methodName] = originalMethod;
}

this._serviceModeState.methodOverrides = {};
}
}

module.exports = BaseService;
17 changes: 17 additions & 0 deletions src/backend/src/services/TestService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const BaseService = require('./BaseService');

class TestService extends BaseService {
method_to_mock () {
return 5;
}

__test_method_to_mock () {
return 7;
}

_test ({ assert }) {
assert.equal(this.method_to_mock(), 7);
}
}

module.exports = { TestService };
27 changes: 27 additions & 0 deletions src/backend/tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,33 @@ class ExampleService extends BaseService {
| `assert.equal` | `actual`, `expected`, `message` | `actual: any`, `expected: any`, `message: string` | Asserts that `actual === expected`. The final parameter is a short descriptive message for the test rule. |


#### Overriding Service Methods

When running under the test kernel, services have the mode
`{ override_prefix: '__test_' }`. This means whenever `some_method` is called
within the service, `__test_some_method` will be called instead if it is
defined. For example, in the following service we

```javascript
class TestService extends BaseService {
normal_method () {
return 3;
}
method_to_mock () {
return 5;
}

__test_method_to_mock () {
return 7;
}

_test ({ assert }) {
// This assertion will pass
assert.equal(this.normal_method(), 3);
assert.equal(this.method_to_mock(), 7);
}
}
```

### Test Kernel Notes

Expand Down
6 changes: 6 additions & 0 deletions src/backend/tools/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ const main = async () => {
console.log(`\x1B[33;1m=== [ Service :: ${name} ] ===\x1B[0m`);
});

// Switch service to test mode (enables __test_ method overrides)
ins.setServiceMode({ override_prefix: '__test_' });

const testapi = {
assert: (condition, name) => {
name = name || condition.toString();
Expand Down Expand Up @@ -262,6 +265,9 @@ const main = async () => {

await ins._test(testapi);

// Restore service to normal mode
ins.setServiceMode(null);

total_passed += passed;
total_failed += failed;
}
Expand Down