@open-rlb/nestjs-amqp 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -5
- package/schematics/add-auth-provider/index.d.ts +3 -0
- package/schematics/add-auth-provider/index.js +60 -0
- package/schematics/add-auth-provider/index.js.map +1 -0
- package/schematics/add-auth-provider/index.spec.ts +83 -0
- package/schematics/add-auth-provider/index.ts +83 -0
- package/schematics/add-auth-provider/schema.d.ts +18 -0
- package/schematics/add-auth-provider/schema.js +3 -0
- package/schematics/add-auth-provider/schema.js.map +1 -0
- package/schematics/add-auth-provider/schema.json +59 -0
- package/schematics/add-auth-provider/schema.ts +35 -0
- package/schematics/add-exchange/index.d.ts +3 -0
- package/schematics/add-exchange/index.js +49 -0
- package/schematics/add-exchange/index.js.map +1 -0
- package/schematics/add-exchange/index.spec.ts +65 -0
- package/schematics/add-exchange/index.ts +65 -0
- package/schematics/add-exchange/schema.d.ts +14 -0
- package/schematics/add-exchange/schema.js +3 -0
- package/schematics/add-exchange/schema.js.map +1 -0
- package/schematics/add-exchange/schema.json +50 -0
- package/schematics/add-exchange/schema.ts +26 -0
- package/schematics/add-exchange-binding/index.d.ts +3 -0
- package/schematics/add-exchange-binding/index.js +53 -0
- package/schematics/add-exchange-binding/index.js.map +1 -0
- package/schematics/add-exchange-binding/index.spec.ts +73 -0
- package/schematics/add-exchange-binding/index.ts +64 -0
- package/schematics/add-exchange-binding/schema.d.ts +8 -0
- package/schematics/add-exchange-binding/schema.js +3 -0
- package/schematics/add-exchange-binding/schema.js.map +1 -0
- package/schematics/add-exchange-binding/schema.json +36 -0
- package/schematics/add-exchange-binding/schema.ts +14 -0
- package/schematics/add-gateway/index.d.ts +4 -0
- package/schematics/add-gateway/index.js +105 -0
- package/schematics/add-gateway/index.js.map +1 -0
- package/schematics/add-gateway/index.spec.ts +163 -0
- package/schematics/add-gateway/index.ts +141 -0
- package/schematics/add-gateway/paths.d.ts +12 -0
- package/schematics/add-gateway/paths.js +87 -0
- package/schematics/add-gateway/paths.js.map +1 -0
- package/schematics/add-gateway/paths.ts +103 -0
- package/schematics/add-gateway/schema.d.ts +13 -0
- package/schematics/add-gateway/schema.js +3 -0
- package/schematics/add-gateway/schema.js.map +1 -0
- package/schematics/add-gateway/schema.json +63 -0
- package/schematics/add-gateway/schema.ts +24 -0
- package/schematics/add-gateway/wiring.d.ts +14 -0
- package/schematics/add-gateway/wiring.js +102 -0
- package/schematics/add-gateway/wiring.js.map +1 -0
- package/schematics/add-gateway/wiring.ts +115 -0
- package/schematics/add-metrics-topic/index.d.ts +3 -0
- package/schematics/add-metrics-topic/index.js +31 -0
- package/schematics/add-metrics-topic/index.js.map +1 -0
- package/schematics/add-metrics-topic/index.spec.ts +79 -0
- package/schematics/add-metrics-topic/index.ts +49 -0
- package/schematics/add-metrics-topic/schema.d.ts +10 -0
- package/schematics/add-metrics-topic/schema.js +3 -0
- package/schematics/add-metrics-topic/schema.js.map +1 -0
- package/schematics/add-metrics-topic/schema.json +48 -0
- package/schematics/add-metrics-topic/schema.ts +18 -0
- package/schematics/add-queue/index.d.ts +3 -0
- package/schematics/add-queue/index.js +48 -0
- package/schematics/add-queue/index.js.map +1 -0
- package/schematics/add-queue/index.spec.ts +75 -0
- package/schematics/add-queue/index.ts +74 -0
- package/schematics/add-queue/schema.d.ts +17 -0
- package/schematics/add-queue/schema.js +3 -0
- package/schematics/add-queue/schema.js.map +1 -0
- package/schematics/add-queue/schema.json +54 -0
- package/schematics/add-queue/schema.ts +32 -0
- package/schematics/add-reply-queue/index.d.ts +3 -0
- package/schematics/add-reply-queue/index.js +26 -0
- package/schematics/add-reply-queue/index.js.map +1 -0
- package/schematics/add-reply-queue/index.spec.ts +69 -0
- package/schematics/add-reply-queue/index.ts +33 -0
- package/schematics/add-reply-queue/schema.d.ts +6 -0
- package/schematics/add-reply-queue/schema.js +3 -0
- package/schematics/add-reply-queue/schema.js.map +1 -0
- package/schematics/add-reply-queue/schema.json +28 -0
- package/schematics/add-reply-queue/schema.ts +10 -0
- package/schematics/add-route/index.d.ts +3 -0
- package/schematics/add-route/index.js +57 -0
- package/schematics/add-route/index.js.map +1 -0
- package/schematics/add-route/index.spec.ts +75 -0
- package/schematics/add-route/index.ts +74 -0
- package/schematics/add-route/schema.d.ts +22 -0
- package/schematics/add-route/schema.js +3 -0
- package/schematics/add-route/schema.js.map +1 -0
- package/schematics/add-route/schema.json +74 -0
- package/schematics/add-route/schema.ts +43 -0
- package/schematics/add-topic/index.d.ts +3 -0
- package/schematics/add-topic/index.js +69 -0
- package/schematics/add-topic/index.js.map +1 -0
- package/schematics/add-topic/index.spec.ts +100 -0
- package/schematics/add-topic/index.ts +104 -0
- package/schematics/add-topic/schema.d.ts +18 -0
- package/schematics/add-topic/schema.js +3 -0
- package/schematics/add-topic/schema.js.map +1 -0
- package/schematics/add-topic/schema.json +63 -0
- package/schematics/add-topic/schema.ts +34 -0
- package/schematics/add-ws-event/index.d.ts +3 -0
- package/schematics/add-ws-event/index.js +53 -0
- package/schematics/add-ws-event/index.js.map +1 -0
- package/schematics/add-ws-event/index.spec.ts +85 -0
- package/schematics/add-ws-event/index.ts +72 -0
- package/schematics/add-ws-event/schema.d.ts +17 -0
- package/schematics/add-ws-event/schema.js +3 -0
- package/schematics/add-ws-event/schema.js.map +1 -0
- package/schematics/add-ws-event/schema.json +53 -0
- package/schematics/add-ws-event/schema.ts +33 -0
- package/schematics/collection.json +81 -1
- package/schematics/configure-broker/index.d.ts +3 -0
- package/schematics/configure-broker/index.js +41 -0
- package/schematics/configure-broker/index.js.map +1 -0
- package/schematics/configure-broker/index.spec.ts +62 -0
- package/schematics/configure-broker/index.ts +51 -0
- package/schematics/configure-broker/schema.d.ts +11 -0
- package/schematics/configure-broker/schema.js +3 -0
- package/schematics/configure-broker/schema.js.map +1 -0
- package/schematics/configure-broker/schema.json +47 -0
- package/schematics/configure-broker/schema.ts +20 -0
- package/schematics/enable-load-config/index.d.ts +3 -0
- package/schematics/enable-load-config/index.js +32 -0
- package/schematics/enable-load-config/index.js.map +1 -0
- package/schematics/enable-load-config/index.spec.ts +61 -0
- package/schematics/enable-load-config/index.ts +39 -0
- package/schematics/enable-load-config/schema.d.ts +8 -0
- package/schematics/enable-load-config/schema.js +3 -0
- package/schematics/enable-load-config/schema.js.map +1 -0
- package/schematics/enable-load-config/schema.json +36 -0
- package/schematics/enable-load-config/schema.ts +14 -0
- package/schematics/enable-retry/index.d.ts +3 -0
- package/schematics/enable-retry/index.js +51 -0
- package/schematics/enable-retry/index.js.map +1 -0
- package/schematics/enable-retry/index.spec.ts +73 -0
- package/schematics/enable-retry/index.ts +62 -0
- package/schematics/enable-retry/schema.d.ts +13 -0
- package/schematics/enable-retry/schema.js +3 -0
- package/schematics/enable-retry/schema.js.map +1 -0
- package/schematics/enable-retry/schema.json +64 -0
- package/schematics/enable-retry/schema.ts +24 -0
- package/schematics/enable-route-discovery/index.d.ts +3 -0
- package/schematics/enable-route-discovery/index.js +36 -0
- package/schematics/enable-route-discovery/index.js.map +1 -0
- package/schematics/enable-route-discovery/index.spec.ts +55 -0
- package/schematics/enable-route-discovery/index.ts +48 -0
- package/schematics/enable-route-discovery/schema.d.ts +9 -0
- package/schematics/enable-route-discovery/schema.js +3 -0
- package/schematics/enable-route-discovery/schema.js.map +1 -0
- package/schematics/enable-route-discovery/schema.json +44 -0
- package/schematics/enable-route-discovery/schema.ts +16 -0
- package/schematics/harden-gateway/index.d.ts +3 -0
- package/schematics/harden-gateway/index.js +93 -0
- package/schematics/harden-gateway/index.js.map +1 -0
- package/schematics/harden-gateway/index.spec.ts +105 -0
- package/schematics/harden-gateway/index.ts +108 -0
- package/schematics/harden-gateway/schema.d.ts +11 -0
- package/schematics/harden-gateway/schema.js +3 -0
- package/schematics/harden-gateway/schema.js.map +1 -0
- package/schematics/harden-gateway/schema.json +47 -0
- package/schematics/harden-gateway/schema.ts +20 -0
- package/schematics/nest-add/index.js +55 -832
- package/schematics/nest-add/index.js.map +1 -1
- package/schematics/nest-add/index.spec.ts +73 -0
- package/schematics/nest-add/index.ts +76 -917
- package/schematics/nest-add/init.schema.d.ts +1 -0
- package/schematics/nest-add/init.schema.ts +3 -0
- package/schematics/nest-add/schema.json +4 -43
- package/schematics/set-connection-name/index.d.ts +3 -0
- package/schematics/set-connection-name/index.js +34 -0
- package/schematics/set-connection-name/index.js.map +1 -0
- package/schematics/set-connection-name/index.spec.ts +48 -0
- package/schematics/set-connection-name/index.ts +42 -0
- package/schematics/set-connection-name/schema.d.ts +5 -0
- package/schematics/set-connection-name/schema.js +3 -0
- package/schematics/set-connection-name/schema.js.map +1 -0
- package/schematics/set-connection-name/schema.json +24 -0
- package/schematics/set-connection-name/schema.ts +8 -0
- package/schematics/utils/broker-yaml.util.d.ts +53 -0
- package/schematics/utils/broker-yaml.util.js +91 -0
- package/schematics/utils/broker-yaml.util.js.map +1 -0
- package/schematics/utils/broker-yaml.util.ts +172 -0
- package/schematics/utils/nest-wiring.util.d.ts +30 -0
- package/schematics/utils/nest-wiring.util.js +170 -0
- package/schematics/utils/nest-wiring.util.js.map +1 -0
- package/schematics/utils/nest-wiring.util.ts +211 -0
- package/schematics/utils/schematic-prompt.util.d.ts +7 -0
- package/schematics/utils/schematic-prompt.util.js +57 -0
- package/schematics/utils/schematic-prompt.util.js.map +1 -0
- package/schematics/utils/schematic-prompt.util.ts +82 -0
- package/schematics/utils/yaml-config.util.d.ts +22 -0
- package/schematics/utils/yaml-config.util.js +119 -0
- package/schematics/utils/yaml-config.util.js.map +1 -0
- package/schematics/utils/yaml-config.util.ts +167 -0
- /package/schematics/{nest-add → add-gateway}/files/acl/src/cache/in-memory-acl-store.ts +0 -0
- /package/schematics/{nest-add → add-gateway}/files/acl/src/modules/database/repository/acl.repository.ts +0 -0
- /package/schematics/{nest-add → add-gateway}/files/db-core/src/modules/database/repository/in-memory-collection.ts +0 -0
- /package/schematics/{nest-add → add-gateway}/files/gateway-admin/src/modules/database/repository/gateway.repository.ts +0 -0
- /package/schematics/{nest-add → add-gateway}/files/gateway-admin/src/modules/database/repository/route-sync.repository.ts +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** HTTP verb for the route. Mirrors PathDefinition['method']. */
|
|
2
|
+
export type RouteMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
3
|
+
/** Where the gateway reads the RPC payload from. Mirrors BrokerHttpDataSource. */
|
|
4
|
+
export type RouteDataSource = 'body' | 'query' | 'params' | 'body-query' | 'query-body';
|
|
5
|
+
/** rpc = wait for the reply; event = fire-and-forget (confirm only). */
|
|
6
|
+
export type RouteMode = 'rpc' | 'event';
|
|
7
|
+
|
|
8
|
+
export interface AddRouteOptions {
|
|
9
|
+
/** Route name (the key). Prompted when omitted. */
|
|
10
|
+
name?: string;
|
|
11
|
+
/** HTTP method. Default: GET. */
|
|
12
|
+
method?: RouteMethod;
|
|
13
|
+
/** URL path (e.g. /orders/:id). Prompted when omitted. */
|
|
14
|
+
path?: string;
|
|
15
|
+
/** Where to read the payload from. Default: query for GET, body otherwise. */
|
|
16
|
+
dataSource?: RouteDataSource;
|
|
17
|
+
/** Broker topic the request forwards to. Prompted when omitted. */
|
|
18
|
+
topic?: string;
|
|
19
|
+
/** Broker action invoked on that topic. Prompted when omitted. */
|
|
20
|
+
action?: string;
|
|
21
|
+
/** rpc (wait reply) | event (fire-and-forget). Default: rpc. */
|
|
22
|
+
mode?: RouteMode;
|
|
23
|
+
/** RPC reply timeout in ms. */
|
|
24
|
+
timeout?: number;
|
|
25
|
+
/** Auth-provider name that must verify the caller. */
|
|
26
|
+
auth?: string;
|
|
27
|
+
/** Allow anonymous callers even when `auth` is set. */
|
|
28
|
+
allowAnonymous?: boolean;
|
|
29
|
+
/** Actions the caller must hold (OR-semantics) — gated via the ACL. Requires `auth`. */
|
|
30
|
+
actions?: string | string[];
|
|
31
|
+
/** HTTP status returned on success. */
|
|
32
|
+
successStatusCode?: number;
|
|
33
|
+
/** Return the reply as a raw binary buffer. */
|
|
34
|
+
binary?: boolean;
|
|
35
|
+
/** Respond with this redirect status instead of a body. */
|
|
36
|
+
redirect?: number;
|
|
37
|
+
/** Forward the raw (unparsed) request body — needs rawBody:true at bootstrap. */
|
|
38
|
+
parseRaw?: boolean;
|
|
39
|
+
/** Update the entry when it already exists (default: leave it untouched). */
|
|
40
|
+
overwrite?: boolean;
|
|
41
|
+
/** Path to config.yaml (default: auto-detected, typically config/config.yaml). */
|
|
42
|
+
config?: string;
|
|
43
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.main = main;
|
|
4
|
+
const broker_yaml_util_1 = require("../utils/broker-yaml.util");
|
|
5
|
+
const schematic_prompt_util_1 = require("../utils/schematic-prompt.util");
|
|
6
|
+
const yaml_config_util_1 = require("../utils/yaml-config.util");
|
|
7
|
+
const MODES = ['rpc', 'handle', 'broadcast', 'event'];
|
|
8
|
+
function main(options) {
|
|
9
|
+
return async (tree, context) => {
|
|
10
|
+
const flagsProvided = options.name !== undefined;
|
|
11
|
+
const prompts = (0, schematic_prompt_util_1.loadPrompts)(context, flagsProvided);
|
|
12
|
+
const name = options.name || (await (0, schematic_prompt_util_1.askText)(prompts, 'Topic name?', ''));
|
|
13
|
+
if (!name) {
|
|
14
|
+
context.logger.error('[rlb-amqp] add-topic: a name is required.');
|
|
15
|
+
return tree;
|
|
16
|
+
}
|
|
17
|
+
const mode = options.mode || (await (0, schematic_prompt_util_1.askSelect)(prompts, 'Topic mode?', MODES, 'rpc'));
|
|
18
|
+
const exchange = options.exchange || (await (0, schematic_prompt_util_1.askText)(prompts, 'Exchange?', 'rlb'));
|
|
19
|
+
const createDeps = options.createDeps ?? true;
|
|
20
|
+
const configPath = (0, yaml_config_util_1.findConfigYaml)(tree, options.config);
|
|
21
|
+
const { doc, existed } = (0, yaml_config_util_1.readConfigDoc)(tree, configPath);
|
|
22
|
+
if (!existed)
|
|
23
|
+
context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
|
|
24
|
+
const consumesQueue = mode === 'rpc' || mode === 'handle';
|
|
25
|
+
let queue = options.queue;
|
|
26
|
+
if (consumesQueue && !queue) {
|
|
27
|
+
queue = await (0, schematic_prompt_util_1.askText)(prompts, `Queue for '${name}'?`, name);
|
|
28
|
+
}
|
|
29
|
+
if (createDeps && !(0, broker_yaml_util_1.exchangeExists)(doc, exchange)) {
|
|
30
|
+
const exOutcome = (0, broker_yaml_util_1.ensureExchange)(doc, { name: exchange });
|
|
31
|
+
(0, schematic_prompt_util_1.logOutcome)(context, `exchange '${exchange}'`, exOutcome);
|
|
32
|
+
}
|
|
33
|
+
const routingKey = (0, broker_yaml_util_1.routingKeyRequired)(doc, exchange) ? options.routingKey || name : options.routingKey;
|
|
34
|
+
if (createDeps && queue && !(0, broker_yaml_util_1.queueExists)(doc, queue)) {
|
|
35
|
+
const qOutcome = (0, broker_yaml_util_1.ensureQueue)(doc, {
|
|
36
|
+
name: queue,
|
|
37
|
+
exchange,
|
|
38
|
+
routingKey: (0, broker_yaml_util_1.routingKeyRequired)(doc, exchange) ? routingKey || queue : options.routingKey,
|
|
39
|
+
});
|
|
40
|
+
(0, schematic_prompt_util_1.logOutcome)(context, `queue '${queue}'`, qOutcome);
|
|
41
|
+
}
|
|
42
|
+
const hasRetry = options.retryMaxAttempts !== undefined ||
|
|
43
|
+
options.retryDelayMs !== undefined ||
|
|
44
|
+
options.retryOnExhausted !== undefined;
|
|
45
|
+
const retry = hasRetry
|
|
46
|
+
? {
|
|
47
|
+
maxAttempts: options.retryMaxAttempts,
|
|
48
|
+
delayMs: options.retryDelayMs,
|
|
49
|
+
onExhausted: options.retryOnExhausted,
|
|
50
|
+
}
|
|
51
|
+
: undefined;
|
|
52
|
+
const outcome = (0, broker_yaml_util_1.ensureTopic)(doc, {
|
|
53
|
+
name,
|
|
54
|
+
mode,
|
|
55
|
+
queue,
|
|
56
|
+
exchange,
|
|
57
|
+
routingKey,
|
|
58
|
+
errorBehavior: options.errorBehavior,
|
|
59
|
+
retry,
|
|
60
|
+
mandatory: options.mandatory,
|
|
61
|
+
persistent: options.persistent,
|
|
62
|
+
toObservable: options.toObservable,
|
|
63
|
+
}, { overwrite: options.overwrite });
|
|
64
|
+
(0, schematic_prompt_util_1.logOutcome)(context, `topic '${name}'`, outcome);
|
|
65
|
+
(0, yaml_config_util_1.writeConfigDoc)(tree, configPath, doc);
|
|
66
|
+
return tree;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/add-topic/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
|
2
|
+
import { parseDocument } from 'yaml';
|
|
3
|
+
import { main } from './index';
|
|
4
|
+
import { AddTopicOptions } from './schema';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Invoke the factory Rule directly against a Tree (NOT SchematicTestRunner — it eagerly loads `ora`,
|
|
8
|
+
* an ESM-only module jest cannot transform here). Passing `name` makes `flagsProvided` true, so no
|
|
9
|
+
* interactive prompt is attempted.
|
|
10
|
+
*/
|
|
11
|
+
const ctx = { logger: { info() {}, warn() {}, error() {} } } as unknown as SchematicContext;
|
|
12
|
+
|
|
13
|
+
async function run(options: AddTopicOptions, tree: Tree): Promise<Tree> {
|
|
14
|
+
const rule = main(options) as Rule;
|
|
15
|
+
const result = await (rule as (t: Tree, c: SchematicContext) => Promise<Tree>)(tree, ctx);
|
|
16
|
+
return result || tree;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function read(tree: Tree, path = 'config/config.yaml'): string {
|
|
20
|
+
return tree.read(path)!.toString('utf-8');
|
|
21
|
+
}
|
|
22
|
+
function doc(tree: Tree) {
|
|
23
|
+
return parseDocument(read(tree));
|
|
24
|
+
}
|
|
25
|
+
function seedConfig(): Tree {
|
|
26
|
+
const tree = Tree.empty();
|
|
27
|
+
tree.create(
|
|
28
|
+
'config/config.yaml',
|
|
29
|
+
['# my config', 'broker:', ' exchanges:', ' - name: rlb', ' type: direct', 'topics: []', ''].join('\n'),
|
|
30
|
+
);
|
|
31
|
+
return tree;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('add-topic schematic', () => {
|
|
35
|
+
it('appends a new topic, preserving comments', async () => {
|
|
36
|
+
const tree = await run({ name: 'get-user', mode: 'rpc', queue: 'get-user-q' }, seedConfig());
|
|
37
|
+
const topics = doc(tree).getIn(['topics']) as any;
|
|
38
|
+
expect(topics.items.map((i: any) => String(i.get('name')))).toEqual(['get-user']);
|
|
39
|
+
expect(String(topics.items[0].get('mode'))).toBe('rpc');
|
|
40
|
+
expect(read(tree)).toContain('# my config');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('is idempotent: re-adding the same topic leaves a single entry', async () => {
|
|
44
|
+
let tree = await run({ name: 'get-user', mode: 'rpc', queue: 'get-user-q' }, seedConfig());
|
|
45
|
+
tree = await run({ name: 'get-user', mode: 'rpc', queue: 'get-user-q' }, tree);
|
|
46
|
+
const topics = doc(tree).getIn(['topics']) as any;
|
|
47
|
+
const names = topics.items.map((i: any) => String(i.get('name')));
|
|
48
|
+
expect(names.filter((n: string) => n === 'get-user')).toHaveLength(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('creates the consuming queue for rpc mode', async () => {
|
|
52
|
+
const tree = await run({ name: 'get-user', mode: 'rpc', queue: 'get-user-q' }, seedConfig());
|
|
53
|
+
const queues = doc(tree).getIn(['broker', 'queues']) as any;
|
|
54
|
+
const queue = queues.items.find((i: any) => String(i.get('name')) === 'get-user-q');
|
|
55
|
+
expect(queue).toBeDefined();
|
|
56
|
+
expect(String(queue.get('exchange'))).toBe('rlb');
|
|
57
|
+
const topic = (doc(tree).getIn(['topics']) as any).items[0];
|
|
58
|
+
expect(String(topic.get('queue'))).toBe('get-user-q');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('does not require a queue for broadcast mode but binds the exchange', async () => {
|
|
62
|
+
const tree = await run({ name: 'announce', mode: 'broadcast', exchange: 'events' }, seedConfig());
|
|
63
|
+
const topic = (doc(tree).getIn(['topics']) as any).items[0];
|
|
64
|
+
expect(String(topic.get('mode'))).toBe('broadcast');
|
|
65
|
+
expect(String(topic.get('exchange'))).toBe('events');
|
|
66
|
+
expect(topic.get('queue')).toBeUndefined();
|
|
67
|
+
const exchanges = doc(tree).getIn(['broker', 'exchanges']) as any;
|
|
68
|
+
expect(exchanges.items.map((i: any) => String(i.get('name')))).toContain('events');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('builds a retry object only when a retry flag is provided', async () => {
|
|
72
|
+
const tree = await run(
|
|
73
|
+
{ name: 'flaky', mode: 'handle', queue: 'flaky-q', retryMaxAttempts: 5, retryDelayMs: 1000, retryOnExhausted: 'drop' },
|
|
74
|
+
seedConfig(),
|
|
75
|
+
);
|
|
76
|
+
const topic = (doc(tree).getIn(['topics']) as any).items[0];
|
|
77
|
+
const retry = topic.get('retry') as any;
|
|
78
|
+
expect(Number(retry.get('maxAttempts'))).toBe(5);
|
|
79
|
+
expect(Number(retry.get('delayMs'))).toBe(1000);
|
|
80
|
+
expect(String(retry.get('onExhausted'))).toBe('drop');
|
|
81
|
+
expect(retry.get('deadLetter')).toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('omits retry entirely when no retry flag is given', async () => {
|
|
85
|
+
const tree = await run({ name: 'get-user', mode: 'rpc', queue: 'get-user-q' }, seedConfig());
|
|
86
|
+
const topic = (doc(tree).getIn(['topics']) as any).items[0];
|
|
87
|
+
expect(topic.get('retry')).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('defaults routingKey to the topic name for topic exchanges', async () => {
|
|
91
|
+
const seed = Tree.empty();
|
|
92
|
+
seed.create(
|
|
93
|
+
'config/config.yaml',
|
|
94
|
+
['broker:', ' exchanges:', ' - name: rlb', ' type: topic', 'topics: []', ''].join('\n'),
|
|
95
|
+
);
|
|
96
|
+
const tree = await run({ name: 'get-user', mode: 'rpc', queue: 'get-user-q', exchange: 'rlb' }, seed);
|
|
97
|
+
const topic = (doc(tree).getIn(['topics']) as any).items[0];
|
|
98
|
+
expect(String(topic.get('routingKey'))).toBe('get-user');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
|
2
|
+
import {
|
|
3
|
+
ensureExchange,
|
|
4
|
+
ensureQueue,
|
|
5
|
+
ensureTopic,
|
|
6
|
+
exchangeExists,
|
|
7
|
+
queueExists,
|
|
8
|
+
RetrySpec,
|
|
9
|
+
routingKeyRequired,
|
|
10
|
+
TopicMode,
|
|
11
|
+
} from '../utils/broker-yaml.util';
|
|
12
|
+
import { askConfirm, askSelect, askText, loadPrompts, logOutcome } from '../utils/schematic-prompt.util';
|
|
13
|
+
import { findConfigYaml, readConfigDoc, writeConfigDoc } from '../utils/yaml-config.util';
|
|
14
|
+
import { AddTopicOptions } from './schema';
|
|
15
|
+
|
|
16
|
+
const MODES: readonly TopicMode[] = ['rpc', 'handle', 'broadcast', 'event'];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `add-topic <name>` — idempotent upsert of a `topics[]` entry, comment-preserving.
|
|
20
|
+
* rpc/handle modes consume a queue, so we create the queue (and its exchange) via the ensureQueue/
|
|
21
|
+
* ensureExchange DATA helpers — never sibling schematics — to avoid prompt ping-pong. Any retry* flag
|
|
22
|
+
* builds a `retry` object; the dead-letter exchange itself is owned by the enable-retry schematic, so
|
|
23
|
+
* we deliberately never emit `retry.deadLetter` here.
|
|
24
|
+
*/
|
|
25
|
+
export function main(options: AddTopicOptions): Rule {
|
|
26
|
+
return async (tree: Tree, context: SchematicContext) => {
|
|
27
|
+
const flagsProvided = options.name !== undefined;
|
|
28
|
+
const prompts = loadPrompts(context, flagsProvided);
|
|
29
|
+
|
|
30
|
+
const name = options.name || (await askText(prompts, 'Topic name?', ''));
|
|
31
|
+
if (!name) {
|
|
32
|
+
context.logger.error('[rlb-amqp] add-topic: a name is required.');
|
|
33
|
+
return tree;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const mode = (options.mode as TopicMode) || (await askSelect(prompts, 'Topic mode?', MODES, 'rpc'));
|
|
37
|
+
const exchange = options.exchange || (await askText(prompts, 'Exchange?', 'rlb'));
|
|
38
|
+
const createDeps = options.createDeps ?? true;
|
|
39
|
+
|
|
40
|
+
const configPath = findConfigYaml(tree, options.config);
|
|
41
|
+
const { doc, existed } = readConfigDoc(tree, configPath);
|
|
42
|
+
if (!existed) context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
|
|
43
|
+
|
|
44
|
+
// rpc/handle require a consuming queue; broadcast/event route via exchange(+routingKey).
|
|
45
|
+
const consumesQueue = mode === 'rpc' || mode === 'handle';
|
|
46
|
+
let queue = options.queue;
|
|
47
|
+
if (consumesQueue && !queue) {
|
|
48
|
+
queue = await askText(prompts, `Queue for '${name}'?`, name);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Ensure the exchange first so a created queue never dangles.
|
|
52
|
+
if (createDeps && !exchangeExists(doc, exchange)) {
|
|
53
|
+
const exOutcome = ensureExchange(doc, { name: exchange });
|
|
54
|
+
logOutcome(context, `exchange '${exchange}'`, exOutcome);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Topic exchanges MUST carry a routingKey; default it to the topic name when unspecified.
|
|
58
|
+
const routingKey = routingKeyRequired(doc, exchange) ? options.routingKey || name : options.routingKey;
|
|
59
|
+
|
|
60
|
+
if (createDeps && queue && !queueExists(doc, queue)) {
|
|
61
|
+
const qOutcome = ensureQueue(doc, {
|
|
62
|
+
name: queue,
|
|
63
|
+
exchange,
|
|
64
|
+
// Match the topic's routingKey when the exchange is topic-typed (helper defaults to queue name otherwise).
|
|
65
|
+
routingKey: routingKeyRequired(doc, exchange) ? routingKey || queue : options.routingKey,
|
|
66
|
+
});
|
|
67
|
+
logOutcome(context, `queue '${queue}'`, qOutcome);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Only build a retry object when the caller supplied at least one retry field.
|
|
71
|
+
const hasRetry =
|
|
72
|
+
options.retryMaxAttempts !== undefined ||
|
|
73
|
+
options.retryDelayMs !== undefined ||
|
|
74
|
+
options.retryOnExhausted !== undefined;
|
|
75
|
+
const retry: RetrySpec | undefined = hasRetry
|
|
76
|
+
? {
|
|
77
|
+
maxAttempts: options.retryMaxAttempts,
|
|
78
|
+
delayMs: options.retryDelayMs,
|
|
79
|
+
onExhausted: options.retryOnExhausted,
|
|
80
|
+
}
|
|
81
|
+
: undefined;
|
|
82
|
+
|
|
83
|
+
const outcome = ensureTopic(
|
|
84
|
+
doc,
|
|
85
|
+
{
|
|
86
|
+
name,
|
|
87
|
+
mode,
|
|
88
|
+
queue,
|
|
89
|
+
exchange,
|
|
90
|
+
routingKey,
|
|
91
|
+
errorBehavior: options.errorBehavior,
|
|
92
|
+
retry,
|
|
93
|
+
mandatory: options.mandatory,
|
|
94
|
+
persistent: options.persistent,
|
|
95
|
+
toObservable: options.toObservable,
|
|
96
|
+
},
|
|
97
|
+
{ overwrite: options.overwrite },
|
|
98
|
+
);
|
|
99
|
+
logOutcome(context, `topic '${name}'`, outcome);
|
|
100
|
+
|
|
101
|
+
writeConfigDoc(tree, configPath, doc);
|
|
102
|
+
return tree;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TopicMode } from '../utils/broker-yaml.util';
|
|
2
|
+
export interface AddTopicOptions {
|
|
3
|
+
name?: string;
|
|
4
|
+
mode?: TopicMode;
|
|
5
|
+
queue?: string;
|
|
6
|
+
exchange?: string;
|
|
7
|
+
routingKey?: string;
|
|
8
|
+
errorBehavior?: 'ack' | 'nack' | 'requeue';
|
|
9
|
+
retryMaxAttempts?: number;
|
|
10
|
+
retryDelayMs?: number;
|
|
11
|
+
retryOnExhausted?: 'dead-letter' | 'drop';
|
|
12
|
+
mandatory?: boolean;
|
|
13
|
+
persistent?: boolean;
|
|
14
|
+
toObservable?: boolean;
|
|
15
|
+
createDeps?: boolean;
|
|
16
|
+
overwrite?: boolean;
|
|
17
|
+
config?: string;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/add-topic/schema.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "SchematicsRlbAmqpAddTopic",
|
|
4
|
+
"title": "Add a broker topic to config.yaml",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "Topic name (the key).",
|
|
10
|
+
"x-prompt": "Topic name?",
|
|
11
|
+
"$default": { "$source": "argv", "index": 0 }
|
|
12
|
+
},
|
|
13
|
+
"mode": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": ["rpc", "handle", "broadcast", "event"],
|
|
16
|
+
"default": "rpc",
|
|
17
|
+
"description": "Topic mode."
|
|
18
|
+
},
|
|
19
|
+
"queue": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Queue the topic consumes (required for rpc/handle)."
|
|
22
|
+
},
|
|
23
|
+
"exchange": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"default": "rlb",
|
|
26
|
+
"description": "Exchange the topic uses."
|
|
27
|
+
},
|
|
28
|
+
"routingKey": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "routingKey (required — and defaulted — when the exchange is a topic)."
|
|
31
|
+
},
|
|
32
|
+
"errorBehavior": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"enum": ["ack", "nack", "requeue"],
|
|
35
|
+
"description": "errorBehavior."
|
|
36
|
+
},
|
|
37
|
+
"retryMaxAttempts": { "type": "number", "description": "retry.maxAttempts." },
|
|
38
|
+
"retryDelayMs": { "type": "number", "description": "retry.delayMs." },
|
|
39
|
+
"retryOnExhausted": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"enum": ["dead-letter", "drop"],
|
|
42
|
+
"description": "retry.onExhausted."
|
|
43
|
+
},
|
|
44
|
+
"mandatory": { "type": "boolean", "description": "options.mandatory." },
|
|
45
|
+
"persistent": { "type": "boolean", "description": "options.persistent." },
|
|
46
|
+
"toObservable": { "type": "boolean", "description": "toObservable." },
|
|
47
|
+
"createDeps": {
|
|
48
|
+
"type": "boolean",
|
|
49
|
+
"default": true,
|
|
50
|
+
"description": "Create the queue/exchange dependencies when missing."
|
|
51
|
+
},
|
|
52
|
+
"overwrite": {
|
|
53
|
+
"type": "boolean",
|
|
54
|
+
"default": false,
|
|
55
|
+
"description": "Update the entry if it already exists (default: leave it untouched)."
|
|
56
|
+
},
|
|
57
|
+
"config": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "Path to config.yaml (default: auto-detected)."
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"required": []
|
|
63
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TopicMode } from '../utils/broker-yaml.util';
|
|
2
|
+
|
|
3
|
+
export interface AddTopicOptions {
|
|
4
|
+
/** Topic name (the key). Prompted when omitted. */
|
|
5
|
+
name?: string;
|
|
6
|
+
/** rpc | handle | broadcast | event. Default: rpc. */
|
|
7
|
+
mode?: TopicMode;
|
|
8
|
+
/** Queue the topic consumes (required for rpc/handle). */
|
|
9
|
+
queue?: string;
|
|
10
|
+
/** Exchange the topic uses. Default: 'rlb'. */
|
|
11
|
+
exchange?: string;
|
|
12
|
+
/** routingKey (required — and defaulted — when the exchange is a topic). */
|
|
13
|
+
routingKey?: string;
|
|
14
|
+
/** errorBehavior: ack | nack | requeue. */
|
|
15
|
+
errorBehavior?: 'ack' | 'nack' | 'requeue';
|
|
16
|
+
/** retry.maxAttempts. */
|
|
17
|
+
retryMaxAttempts?: number;
|
|
18
|
+
/** retry.delayMs. */
|
|
19
|
+
retryDelayMs?: number;
|
|
20
|
+
/** retry.onExhausted: dead-letter | drop. */
|
|
21
|
+
retryOnExhausted?: 'dead-letter' | 'drop';
|
|
22
|
+
/** options.mandatory. */
|
|
23
|
+
mandatory?: boolean;
|
|
24
|
+
/** options.persistent. */
|
|
25
|
+
persistent?: boolean;
|
|
26
|
+
/** toObservable. */
|
|
27
|
+
toObservable?: boolean;
|
|
28
|
+
/** Create the queue/exchange dependencies when missing. Default: true. */
|
|
29
|
+
createDeps?: boolean;
|
|
30
|
+
/** Update the entry when it already exists (default: leave it untouched). */
|
|
31
|
+
overwrite?: boolean;
|
|
32
|
+
/** Path to config.yaml (default: auto-detected, typically config/config.yaml). */
|
|
33
|
+
config?: string;
|
|
34
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.main = main;
|
|
4
|
+
const schematic_prompt_util_1 = require("../utils/schematic-prompt.util");
|
|
5
|
+
const yaml_config_util_1 = require("../utils/yaml-config.util");
|
|
6
|
+
const TYPES = ['ws', 'http'];
|
|
7
|
+
function main(options) {
|
|
8
|
+
return async (tree, context) => {
|
|
9
|
+
const flagsProvided = options.name !== undefined;
|
|
10
|
+
const prompts = (0, schematic_prompt_util_1.loadPrompts)(context, flagsProvided);
|
|
11
|
+
const name = options.name || (await (0, schematic_prompt_util_1.askText)(prompts, 'Event name?', ''));
|
|
12
|
+
if (!name) {
|
|
13
|
+
context.logger.error('[rlb-amqp] add-ws-event: a name is required.');
|
|
14
|
+
return tree;
|
|
15
|
+
}
|
|
16
|
+
const type = options.type || (await (0, schematic_prompt_util_1.askSelect)(prompts, 'Event type?', TYPES, 'ws'));
|
|
17
|
+
const configPath = (0, yaml_config_util_1.findConfigYaml)(tree, options.config);
|
|
18
|
+
const { doc, existed } = (0, yaml_config_util_1.readConfigDoc)(tree, configPath);
|
|
19
|
+
if (!existed)
|
|
20
|
+
context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
|
|
21
|
+
const item = type === 'http'
|
|
22
|
+
? {
|
|
23
|
+
name,
|
|
24
|
+
type,
|
|
25
|
+
url: options.url,
|
|
26
|
+
method: options.httpMethod,
|
|
27
|
+
timeout: options.timeout,
|
|
28
|
+
}
|
|
29
|
+
: {
|
|
30
|
+
name,
|
|
31
|
+
type,
|
|
32
|
+
exchange: options.exchange,
|
|
33
|
+
routingKey: options.routingKey,
|
|
34
|
+
auth: options.auth,
|
|
35
|
+
requireAuth: options.requireAuth,
|
|
36
|
+
actions: options.actions,
|
|
37
|
+
scopeClaim: options.scopeClaim,
|
|
38
|
+
payloadKey: options.payloadKey,
|
|
39
|
+
};
|
|
40
|
+
if (options.scopeClaim && !options.payloadKey) {
|
|
41
|
+
context.logger.warn('[rlb-amqp] add-ws-event: scopeClaim without payloadKey denies every message (per-user isolation needs both).');
|
|
42
|
+
}
|
|
43
|
+
const hasActions = options.actions !== undefined && (!Array.isArray(options.actions) || options.actions.length > 0);
|
|
44
|
+
if (hasActions && !options.auth) {
|
|
45
|
+
context.logger.warn('[rlb-amqp] add-ws-event: actions require auth.');
|
|
46
|
+
}
|
|
47
|
+
const outcome = (0, yaml_config_util_1.upsertSeqItemByKey)(doc, ['gateway', 'events'], 'name', item, { overwrite: options.overwrite });
|
|
48
|
+
(0, schematic_prompt_util_1.logOutcome)(context, `ws-event '${name}'`, outcome);
|
|
49
|
+
(0, yaml_config_util_1.writeConfigDoc)(tree, configPath, doc);
|
|
50
|
+
return tree;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/add-ws-event/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
|
2
|
+
import { parseDocument } from 'yaml';
|
|
3
|
+
import { main } from './index';
|
|
4
|
+
import { AddWsEventOptions } from './schema';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Schematics are tested by invoking the factory Rule directly against a Tree, NOT via
|
|
8
|
+
* SchematicTestRunner (its testing entrypoint eagerly loads `ora`, an ESM-only module jest cannot
|
|
9
|
+
* transform here). Passing `name` makes `flagsProvided` true, so no interactive prompt is attempted.
|
|
10
|
+
*/
|
|
11
|
+
function makeCtx(warnings: string[] = []): SchematicContext {
|
|
12
|
+
return { logger: { info() {}, warn: (m: string) => warnings.push(m), error() {} } } as unknown as SchematicContext;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function run(options: AddWsEventOptions, tree: Tree, ctx = makeCtx()): Promise<Tree> {
|
|
16
|
+
const rule = main(options) as Rule;
|
|
17
|
+
const result = await (rule as (t: Tree, c: SchematicContext) => Promise<Tree>)(tree, ctx);
|
|
18
|
+
return result || tree;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function read(tree: Tree, path = 'config/config.yaml'): string {
|
|
22
|
+
return tree.read(path)!.toString('utf-8');
|
|
23
|
+
}
|
|
24
|
+
function doc(tree: Tree) {
|
|
25
|
+
return parseDocument(read(tree));
|
|
26
|
+
}
|
|
27
|
+
function seedConfig(): Tree {
|
|
28
|
+
const tree = Tree.empty();
|
|
29
|
+
tree.create('config/config.yaml', ['# my config', 'gateway:', ' events: []', ''].join('\n'));
|
|
30
|
+
return tree;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('add-ws-event schematic', () => {
|
|
34
|
+
it('appends a ws event with exchange/routingKey/scoping, preserving comments', async () => {
|
|
35
|
+
const tree = await run(
|
|
36
|
+
{
|
|
37
|
+
name: 'order-updates',
|
|
38
|
+
type: 'ws',
|
|
39
|
+
exchange: 'rlb',
|
|
40
|
+
routingKey: 'order.updated',
|
|
41
|
+
auth: 'keycloak',
|
|
42
|
+
scopeClaim: 'sub',
|
|
43
|
+
payloadKey: 'userId',
|
|
44
|
+
},
|
|
45
|
+
seedConfig(),
|
|
46
|
+
);
|
|
47
|
+
const events = doc(tree).getIn(['gateway', 'events']) as any;
|
|
48
|
+
expect(events.items.map((i: any) => String(i.get('name')))).toEqual(['order-updates']);
|
|
49
|
+
const ev = events.items[0];
|
|
50
|
+
expect(String(ev.get('type'))).toBe('ws');
|
|
51
|
+
expect(String(ev.get('exchange'))).toBe('rlb');
|
|
52
|
+
expect(String(ev.get('scopeClaim'))).toBe('sub');
|
|
53
|
+
expect(read(tree)).toContain('# my config');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('maps httpMethod onto `method` for http events (no ws-only fields leak)', async () => {
|
|
57
|
+
const tree = await run(
|
|
58
|
+
{ name: 'webhook', type: 'http', url: 'https://hooks/x', httpMethod: 'POST', timeout: 5000, exchange: 'ignored' },
|
|
59
|
+
seedConfig(),
|
|
60
|
+
);
|
|
61
|
+
const ev = (doc(tree).getIn(['gateway', 'events']) as any).items[0];
|
|
62
|
+
expect(String(ev.get('method'))).toBe('POST');
|
|
63
|
+
expect(String(ev.get('url'))).toBe('https://hooks/x');
|
|
64
|
+
// ws-only field must not appear on an http event.
|
|
65
|
+
expect(ev.get('exchange')).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('warns when scopeClaim is set without payloadKey (denies every message)', async () => {
|
|
69
|
+
const warnings: string[] = [];
|
|
70
|
+
await run(
|
|
71
|
+
{ name: 'leaky', type: 'ws', exchange: 'rlb', routingKey: 'x', auth: 'kc', scopeClaim: 'sub' },
|
|
72
|
+
seedConfig(),
|
|
73
|
+
makeCtx(warnings),
|
|
74
|
+
);
|
|
75
|
+
expect(warnings.some((w) => w.includes('scopeClaim without payloadKey'))).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('is idempotent and creates config.yaml when missing', async () => {
|
|
79
|
+
let tree = await run({ name: 'e1', type: 'ws', exchange: 'rlb', routingKey: 'k' }, Tree.empty());
|
|
80
|
+
expect(tree.exists('config/config.yaml')).toBe(true);
|
|
81
|
+
tree = await run({ name: 'e1', type: 'ws', exchange: 'rlb', routingKey: 'k' }, tree);
|
|
82
|
+
const names = (doc(tree).getIn(['gateway', 'events']) as any).items.map((i: any) => String(i.get('name')));
|
|
83
|
+
expect(names.filter((n: string) => n === 'e1')).toHaveLength(1);
|
|
84
|
+
});
|
|
85
|
+
});
|