@hahnpro/flow-sdk 9.6.4 → 2025.2.0-beta.1

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +904 -0
  2. package/jest.config.ts +10 -0
  3. package/package.json +7 -20
  4. package/project.json +41 -0
  5. package/src/index.ts +15 -0
  6. package/src/lib/ContextManager.ts +111 -0
  7. package/src/lib/FlowApplication.ts +659 -0
  8. package/src/lib/FlowElement.ts +220 -0
  9. package/src/lib/FlowEvent.ts +73 -0
  10. package/src/lib/FlowLogger.ts +131 -0
  11. package/src/lib/FlowModule.ts +18 -0
  12. package/src/lib/RpcClient.ts +99 -0
  13. package/src/lib/TestModule.ts +14 -0
  14. package/src/lib/__pycache__/rpc_server.cpython-310.pyc +0 -0
  15. package/src/lib/amqp.ts +32 -0
  16. package/src/lib/extra-validators.ts +62 -0
  17. package/src/lib/flow.interface.ts +56 -0
  18. package/{dist/index.d.ts → src/lib/index.ts} +3 -0
  19. package/src/lib/nats.ts +140 -0
  20. package/src/lib/unit-decorators.ts +156 -0
  21. package/src/lib/unit-utils.ts +163 -0
  22. package/src/lib/units.ts +587 -0
  23. package/src/lib/utils.ts +176 -0
  24. package/test/context-manager-purpose.spec.ts +248 -0
  25. package/test/context-manager.spec.ts +55 -0
  26. package/test/context.spec.ts +180 -0
  27. package/test/event.spec.ts +155 -0
  28. package/test/extra-validators.spec.ts +84 -0
  29. package/test/flow-logger.spec.ts +104 -0
  30. package/test/flow.spec.ts +508 -0
  31. package/test/input-stream.decorator.spec.ts +379 -0
  32. package/test/long-rpc.test.py +14 -0
  33. package/test/long-running-rpc.spec.ts +60 -0
  34. package/test/message.spec.ts +57 -0
  35. package/test/mocks/logger.mock.ts +7 -0
  36. package/test/mocks/nats-connection.mock.ts +135 -0
  37. package/test/mocks/nats-prepare.reals-nats.ts +15 -0
  38. package/test/rpc.spec.ts +198 -0
  39. package/test/rpc.test.py +45 -0
  40. package/test/rx.spec.ts +92 -0
  41. package/test/unit-decorator.spec.ts +57 -0
  42. package/test/utils.spec.ts +210 -0
  43. package/test/validation.spec.ts +174 -0
  44. package/tsconfig.json +13 -0
  45. package/tsconfig.lib.json +22 -0
  46. package/tsconfig.spec.json +8 -0
  47. package/LICENSE +0 -21
  48. package/dist/ContextManager.d.ts +0 -40
  49. package/dist/ContextManager.js +0 -77
  50. package/dist/FlowApplication.d.ts +0 -85
  51. package/dist/FlowApplication.js +0 -500
  52. package/dist/FlowElement.d.ts +0 -67
  53. package/dist/FlowElement.js +0 -163
  54. package/dist/FlowEvent.d.ts +0 -25
  55. package/dist/FlowEvent.js +0 -71
  56. package/dist/FlowLogger.d.ts +0 -44
  57. package/dist/FlowLogger.js +0 -94
  58. package/dist/FlowModule.d.ts +0 -7
  59. package/dist/FlowModule.js +0 -13
  60. package/dist/RpcClient.d.ts +0 -13
  61. package/dist/RpcClient.js +0 -84
  62. package/dist/TestModule.d.ts +0 -2
  63. package/dist/TestModule.js +0 -27
  64. package/dist/amqp.d.ts +0 -14
  65. package/dist/amqp.js +0 -12
  66. package/dist/extra-validators.d.ts +0 -1
  67. package/dist/extra-validators.js +0 -51
  68. package/dist/flow.interface.d.ts +0 -48
  69. package/dist/flow.interface.js +0 -9
  70. package/dist/index.js +0 -18
  71. package/dist/nats.d.ts +0 -12
  72. package/dist/nats.js +0 -109
  73. package/dist/unit-decorators.d.ts +0 -39
  74. package/dist/unit-decorators.js +0 -156
  75. package/dist/unit-utils.d.ts +0 -8
  76. package/dist/unit-utils.js +0 -143
  77. package/dist/units.d.ts +0 -31
  78. package/dist/units.js +0 -570
  79. package/dist/utils.d.ts +0 -51
  80. package/dist/utils.js +0 -137
  81. /package/{dist → src/lib}/rpc_server.py +0 -0
@@ -0,0 +1,198 @@
1
+ import { join } from 'path';
2
+ import { setTimeout } from 'timers/promises';
3
+
4
+ import type { PythonShell } from 'python-shell';
5
+
6
+ import { FlowApplication, FlowEvent, FlowFunction, FlowModule, FlowResource, InputStream } from '../src';
7
+ import { loggerMock } from './mocks/logger.mock';
8
+
9
+ /* eslint-disable no-console */
10
+ describe('Flow RPC', () => {
11
+ let flowApp: FlowApplication;
12
+
13
+ beforeEach(async () => {
14
+ const flow = {
15
+ elements: [
16
+ { id: 'testResource', module: 'test-module', functionFqn: 'test.resource.TestResource' },
17
+ { id: 'testResource2', module: 'test-module', functionFqn: 'test.resource.TestResource' },
18
+ ],
19
+ connections: [
20
+ { id: 'testConnection1', source: 'testTrigger', sourceStream: 'a', target: 'testResource', targetStream: 'a' },
21
+ { id: 'testConnection2', source: 'testTrigger', sourceStream: 'b', target: 'testResource2', targetStream: 'b' },
22
+ { id: 'testConnection1', source: 'testTrigger', sourceStream: 'b', target: 'testResource', targetStream: 'b' },
23
+ { id: 'testConnection1', source: 'testTrigger', sourceStream: 'c', target: 'testResource', targetStream: 'c' },
24
+ { id: 'testConnection1', source: 'testTrigger', sourceStream: 'd', target: 'testResource', targetStream: 'd' },
25
+ { id: 'testConnection1', source: 'testTrigger', sourceStream: 'e', target: 'testResource', targetStream: 'e' },
26
+ { id: 'testConnection1', source: 'testTrigger', sourceStream: 'f', target: 'testResource', targetStream: 'f' },
27
+ ],
28
+ context: {
29
+ flowId: 'testFlow',
30
+ deploymentId: 'testDeployment',
31
+ },
32
+ };
33
+ flowApp = new FlowApplication([TestModule], flow, {
34
+ amqpConfig: {},
35
+ skipApi: true,
36
+ explicitInit: true,
37
+ logger: loggerMock,
38
+ });
39
+ await flowApp.init();
40
+ await setTimeout(2000);
41
+ });
42
+
43
+ test('FLOW.RPC.1 publish message', (done) => {
44
+ flowApp.subscribe('testResource.a', {
45
+ next: async (event: FlowEvent) => {
46
+ expect(event.getData()).toEqual({ res: 'foo' });
47
+ await flowApp.destroy();
48
+ done();
49
+ },
50
+ });
51
+
52
+ flowApp.emit(new FlowEvent({ id: 'testTrigger' }, {}, 'a'));
53
+ });
54
+
55
+ test('FLOW.RPC.2 return sent value', (done) => {
56
+ flowApp.subscribe('testResource.b', {
57
+ next: async (event: FlowEvent) => {
58
+ expect(event.getData()).toEqual(expect.objectContaining({ res: 'bar' }));
59
+ await flowApp.destroy();
60
+ done();
61
+ },
62
+ });
63
+
64
+ flowApp.emit(new FlowEvent({ id: 'testTrigger' }, { value: 'bar' }, 'b'));
65
+ });
66
+
67
+ test('FLOW.RPC.3 error in remote procedure', (done) => {
68
+ flowApp.subscribe('testResource.c', {
69
+ next: async (event: FlowEvent) => {
70
+ console.log(event.getData());
71
+ expect(event.getData().err).toBeDefined();
72
+ await flowApp.destroy();
73
+ done();
74
+ },
75
+ });
76
+
77
+ flowApp.emit(new FlowEvent({ id: 'testTrigger' }, {}, 'c'));
78
+ });
79
+
80
+ test('FLOW.RPC.4 should return argument', (done) => {
81
+ flowApp.subscribe('testResource.d', {
82
+ next: async (event: FlowEvent) => {
83
+ expect(event.getData()).toEqual({ res: '10' });
84
+ await flowApp.destroy();
85
+ done();
86
+ },
87
+ });
88
+
89
+ flowApp.emit(new FlowEvent({ id: 'testTrigger' }, {}, 'd'));
90
+ });
91
+
92
+ test('FLOW.RPC.5 rpc function does not exist', (done) => {
93
+ flowApp.subscribe('testResource.e', {
94
+ next: async (event: FlowEvent) => {
95
+ expect(event.getData().err).toBeDefined();
96
+ await flowApp.destroy();
97
+ done();
98
+ },
99
+ });
100
+
101
+ flowApp.emit(new FlowEvent({ id: 'testTrigger' }, {}, 'e'));
102
+ });
103
+
104
+ test('FLOW.RPC.6 rpc function returns numpy object', (done) => {
105
+ flowApp.subscribe('testResource.f', {
106
+ next: async (event: FlowEvent) => {
107
+ expect(event.getData()).toEqual({ res: '100000000' });
108
+ await flowApp.destroy();
109
+ done();
110
+ },
111
+ });
112
+
113
+ flowApp.emit(
114
+ new FlowEvent(
115
+ { id: 'testTrigger' },
116
+ {
117
+ powers: [
118
+ { timestamp: 1234, powers: 567 },
119
+ { timestamp: 89, powers: 1011 },
120
+ ],
121
+ },
122
+ 'f',
123
+ ),
124
+ );
125
+ });
126
+ });
127
+
128
+ @FlowFunction('test.resource.TestResource')
129
+ class TestResource extends FlowResource {
130
+ private shell: PythonShell;
131
+
132
+ constructor(context) {
133
+ super(context);
134
+ this.shell = this.runPyRpcScript(join(__dirname, 'rpc.test.py'), 10);
135
+ this.shell.on('error', (error) => console.log(error));
136
+ this.shell.on('pythonError', (error) => console.log(error));
137
+ this.shell.on('stderr', (error) => console.log(error));
138
+ this.shell.on('message', (error) => console.log(error));
139
+ }
140
+
141
+ onDestroy = () => this.shell.kill();
142
+
143
+ @InputStream('a')
144
+ public async onA(event) {
145
+ this.callRpcFunction('testA')
146
+ .then((res: any) => {
147
+ this.emitEvent({ res }, event, 'a');
148
+ })
149
+ .catch((err) => console.error(err));
150
+ }
151
+
152
+ @InputStream('b')
153
+ public async onB(event) {
154
+ this.callRpcFunction('testB', 'bar')
155
+ .then((res: any) => this.emitEvent({ res }, event, 'b'))
156
+ .catch((err) => this.logger.error(err));
157
+ }
158
+
159
+ @InputStream('c')
160
+ public async onC(event) {
161
+ this.callRpcFunction('testC')
162
+ .then((res: any) => this.emitEvent({ res }, event, 'c'))
163
+ .catch((err) => this.emitEvent({ err }, event, 'c'));
164
+ }
165
+
166
+ @InputStream('d')
167
+ public async onD(event) {
168
+ this.callRpcFunction('testD')
169
+ .then((res: any) => this.emitEvent({ res }, event, 'd'))
170
+ .catch((err) => this.emitEvent({ err }, event, 'd'));
171
+ }
172
+
173
+ @InputStream('e')
174
+ public async onE(event) {
175
+ this.callRpcFunction('testE')
176
+ .then((res: any) => this.emitEvent({ res }, event, 'e'))
177
+ .catch((err) => {
178
+ this.logger.error(err);
179
+ this.emitEvent({ err }, event, 'e');
180
+ });
181
+ }
182
+
183
+ @InputStream('f')
184
+ public async onF(event) {
185
+ this.callRpcFunction('testF')
186
+ .then((res: any) => this.emitOutput({ res }, 'f'))
187
+ .catch((err) => {
188
+ this.logger.error(err);
189
+ this.emitOutput({ err }, 'f');
190
+ });
191
+ }
192
+ }
193
+
194
+ @FlowModule({
195
+ name: 'test-module',
196
+ declarations: [TestResource],
197
+ })
198
+ class TestModule {}
@@ -0,0 +1,45 @@
1
+ import sys
2
+ import numpy as np
3
+ import json
4
+
5
+ sys.path.append(sys.argv[1])
6
+ from rpc_server import RemoteProcedure, start_consumer
7
+
8
+
9
+ class NpEncoder(json.JSONEncoder):
10
+ def default(self, obj):
11
+ if isinstance(obj, np.integer):
12
+ return int(obj)
13
+ if isinstance(obj, np.floating):
14
+ return float(obj)
15
+ if isinstance(obj, np.ndarray):
16
+ return obj.tolist()
17
+ return json.JSONEncoder.default(self, obj)
18
+
19
+
20
+ @RemoteProcedure
21
+ def testA():
22
+ return "foo"
23
+
24
+
25
+ @RemoteProcedure
26
+ def testB(arg):
27
+ return arg
28
+
29
+
30
+ @RemoteProcedure
31
+ def testC():
32
+ return 1 / 0
33
+
34
+
35
+ @RemoteProcedure
36
+ def testD():
37
+ return sys.argv[3]
38
+
39
+
40
+ @RemoteProcedure
41
+ def testF():
42
+ return np.power(100, 4, dtype=np.int64), NpEncoder
43
+
44
+
45
+ start_consumer(sys.argv[2])
@@ -0,0 +1,92 @@
1
+ import { interval, Subject } from 'rxjs';
2
+ import { take } from 'rxjs/operators';
3
+
4
+ import { delay, FlowApplication, FlowEvent, FlowFunction, FlowModule, FlowResource, FlowTask, InputStream } from '../src';
5
+ import { loggerMock } from './mocks/logger.mock';
6
+
7
+ /* eslint-disable no-console */
8
+ describe('rx', () => {
9
+ test('FLOW.RX.1 rx', (done) => {
10
+ const size = 3;
11
+ const flow = {
12
+ elements: [
13
+ { id: 'testTrigger', module: 'test-module', functionFqn: 'test.resource.TestResource1' },
14
+ { id: 'testResource1', module: 'test-module', functionFqn: 'test.resource.TestResource1' },
15
+ { id: 'testResource2', module: 'test-module', functionFqn: 'test.resource.TestResource2' },
16
+ { id: 'tap', module: 'test-module', functionFqn: 'operators.tap' },
17
+ ],
18
+ connections: [
19
+ { id: 'testConnection1', source: 'testTrigger', target: 'testResource1' },
20
+ { id: 'testConnection2', source: 'testResource1', target: 'testResource2' },
21
+ { id: 'testConnection3', source: 'testResource1', target: 'tap' },
22
+ ],
23
+ };
24
+ const flowApp = new FlowApplication([TestModule], flow, { skipApi: true, logger: loggerMock });
25
+
26
+ flowApp.subscribe('tap.default', {
27
+ next: (event: FlowEvent) => {
28
+ expect(event.getData()).toEqual({ hello: 'world' });
29
+ },
30
+ });
31
+
32
+ let count = 0;
33
+ flowApp.subscribe('testResource2.default', {
34
+ next: (event: FlowEvent) => {
35
+ count++;
36
+ expect(event.getData()).toEqual(expect.objectContaining({ foo: 'bar' }));
37
+ if (count === size) {
38
+ done();
39
+ }
40
+ },
41
+ });
42
+
43
+ const triggers = interval(1000);
44
+ triggers.pipe(take(size)).subscribe((x) => {
45
+ flowApp.emit(new FlowEvent({ id: 'testTrigger' }, {}));
46
+ });
47
+ }, 60000);
48
+ });
49
+
50
+ @FlowFunction('test.resource.TestResource1')
51
+ class TestResource extends FlowResource {
52
+ @InputStream('default')
53
+ public async onDefault(event) {
54
+ await delay(2000);
55
+ return this.emitEvent({ hello: 'world' }, event);
56
+ }
57
+ }
58
+
59
+ @FlowFunction('operators.tap')
60
+ class Tap extends FlowTask {
61
+ tap$: Subject<FlowEvent>;
62
+
63
+ constructor(context) {
64
+ super(context);
65
+ this.tap$ = new Subject<FlowEvent>();
66
+ this.tap$.subscribe({
67
+ next: (event) => {
68
+ this.emitEvent({}, event);
69
+ },
70
+ });
71
+ }
72
+
73
+ @InputStream()
74
+ public async onDefault(event) {
75
+ this.tap$.next(event);
76
+ }
77
+ }
78
+
79
+ @FlowFunction('test.resource.TestResource2')
80
+ class TestResource2 extends FlowResource {
81
+ @InputStream('default', { concurrent: 1 })
82
+ public async onDefault(event) {
83
+ await delay(1000);
84
+ return this.emitEvent({ foo: 'bar' }, event);
85
+ }
86
+ }
87
+
88
+ @FlowModule({
89
+ name: 'test-module',
90
+ declarations: [TestResource, TestResource2, Tap],
91
+ })
92
+ class TestModule {}
@@ -0,0 +1,57 @@
1
+ import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
2
+
3
+ import { IsVoltage } from '../src/lib/unit-decorators';
4
+ import { computeUnitOptions, verifyUnit } from '../src/lib/unit-utils';
5
+
6
+ class Test {
7
+ @IsVoltage('YV')
8
+ arg: number;
9
+ }
10
+
11
+ describe('unit decorators', () => {
12
+ test('FLOW.UD.1 voltage', async () => {
13
+ const schemas = validationMetadatasToSchemas({
14
+ additionalConverters: {
15
+ UnitArgsValidator: (meta) => {
16
+ return {
17
+ measure: meta.constraints[0],
18
+ unit: meta.constraints[1],
19
+ type: 'number',
20
+ };
21
+ },
22
+ },
23
+ });
24
+
25
+ expect(schemas['Test'].properties.arg).toEqual({
26
+ measure: 'voltage',
27
+ unit: 'YV',
28
+ type: 'number',
29
+ });
30
+ });
31
+
32
+ test('FLOW.UD.2 verifyUnit', () => {
33
+ expect(verifyUnit('in', 'length')).toEqual(1 / 39.3701);
34
+ expect(verifyUnit('kNcm', 'torque')).toEqual(10);
35
+ expect(verifyUnit('m/s', 'translationalVelocity')).toEqual(1);
36
+ expect(verifyUnit('km/h', 'translationalVelocity')).toEqual(0.2777777777777778);
37
+ expect(verifyUnit('kgm^2/s^2', 'torque')).toEqual(1);
38
+ expect(verifyUnit('kgkm^2/s^2', 'torque')).toEqual(1e6);
39
+ expect(verifyUnit('1/s', 'electricalFrequency')).toEqual(1);
40
+ expect(verifyUnit('kHz', 'electricalFrequency')).toEqual(1000);
41
+ expect(verifyUnit('W/m^2K', 'thermalTransmittance')).toEqual(1);
42
+ expect(verifyUnit('Btu/hft^2F', 'thermalTransmittance')).toBeCloseTo(5.678);
43
+ expect(verifyUnit('J/hm^2K', 'thermalTransmittance')).toEqual(1 / 3600);
44
+ expect(verifyUnit('ft^2', 'area')).toBeCloseTo(0.0929);
45
+
46
+ expect(verifyUnit('in', 'time')).toEqual(-1);
47
+ expect(verifyUnit('kNmmm', 'torque')).toEqual(-1);
48
+
49
+ expect(verifyUnit('', 'time')).toEqual(-1);
50
+ expect(verifyUnit('min', '')).toEqual(-1);
51
+ expect(verifyUnit('', '')).toEqual(-1);
52
+ });
53
+
54
+ test('FLOW.UD.3 computeUnits', () => {
55
+ expect(computeUnitOptions('L-2')).toEqual(computeUnitOptions('L2'));
56
+ });
57
+ });
@@ -0,0 +1,210 @@
1
+ import { fillTemplate } from '../src';
2
+
3
+ describe('utils', () => {
4
+ test('FLOW.UTIL.1 template string interpolation for flow properties', () => {
5
+ let tmplString = '${test}';
6
+ let tmplVars: any = { test: 'hello world' };
7
+ let filled = fillTemplate(tmplString, tmplVars);
8
+ expect(filled).toEqual('hello world');
9
+
10
+ tmplString = '${foo.bar}';
11
+ tmplVars = { foo: { bar: 'baz' } };
12
+ filled = fillTemplate(tmplString, tmplVars);
13
+ expect(filled).toEqual('baz');
14
+
15
+ tmplString = '${path}';
16
+ tmplVars = { path: 'Some.Random.path' };
17
+ filled = fillTemplate(tmplString, tmplVars);
18
+ expect(filled).toEqual('Some.Random.path');
19
+
20
+ tmplString = 'foo';
21
+ tmplVars = { foo: 'bar' };
22
+ filled = fillTemplate(tmplString, tmplVars);
23
+ expect(filled).toEqual('foo');
24
+
25
+ tmplString = '${foo}';
26
+ tmplVars = { foo: 0 };
27
+ filled = fillTemplate(tmplString, tmplVars);
28
+ expect(filled).toEqual('0');
29
+
30
+ tmplString = '';
31
+ tmplVars = { foo: 'bar' };
32
+ filled = fillTemplate(tmplString, tmplVars);
33
+ expect(filled).toEqual('');
34
+
35
+ tmplString = null;
36
+ tmplVars = null;
37
+ filled = fillTemplate(tmplString, tmplVars);
38
+ expect(filled).toEqual(null);
39
+
40
+ tmplString = '${foo}';
41
+ tmplVars = { foo: 'bar' };
42
+ filled = fillTemplate(tmplString, tmplVars);
43
+ expect(filled).toEqual('bar');
44
+
45
+ tmplString = '4${foo}';
46
+ tmplVars = { foo: 2 };
47
+ filled = fillTemplate(tmplString, tmplVars);
48
+ expect(filled).toEqual('42');
49
+
50
+ tmplString = '${foo}';
51
+ tmplVars = { foo: 42 };
52
+ filled = fillTemplate(tmplString, tmplVars);
53
+ expect(filled).toEqual('42');
54
+
55
+ tmplString = '${foo.bar}';
56
+ tmplVars = { foo: { bar: 'baz' } };
57
+ filled = fillTemplate(tmplString, tmplVars);
58
+ expect(filled).toEqual('baz');
59
+
60
+ tmplString = '${foo}';
61
+ tmplVars = { foo: 'bar', 'a-b': 'x' };
62
+ filled = fillTemplate(tmplString, tmplVars);
63
+ expect(filled).toEqual('bar');
64
+
65
+ tmplString = '${x} ${y} c';
66
+ tmplVars = { x: 'a', y: 'b' };
67
+ filled = fillTemplate(tmplString, tmplVars);
68
+ expect(filled).toEqual('a b c');
69
+
70
+ tmplString = '${x}${y}${z}';
71
+ tmplVars = { x: 'a', z: 'c' };
72
+ filled = fillTemplate(tmplString, tmplVars);
73
+ expect(filled).toEqual('ac');
74
+
75
+ tmplString = '${foo}';
76
+ tmplVars = {};
77
+ filled = fillTemplate(tmplString, tmplVars);
78
+ expect(filled).toEqual(undefined);
79
+
80
+ tmplString = '${foo.bar.baz}';
81
+ tmplVars = {};
82
+ filled = fillTemplate(tmplString, tmplVars);
83
+ expect(filled).toEqual(undefined);
84
+
85
+ tmplString = '${foo}';
86
+ tmplVars = undefined;
87
+ filled = fillTemplate(tmplString, tmplVars);
88
+ expect(filled).toEqual(undefined);
89
+
90
+ tmplVars = null;
91
+ filled = fillTemplate(tmplString, tmplVars);
92
+ expect(filled).toEqual(undefined);
93
+
94
+ tmplString = 'foo';
95
+ filled = fillTemplate(tmplString, tmplVars);
96
+ expect(filled).toEqual('foo');
97
+ });
98
+
99
+ test('FLOW.UTIL.2 deep interpolation', () => {
100
+ let input: any = { foo: '${bar}' };
101
+ let props: any = { bar: 'baz' };
102
+ let res = fillTemplate(input, props);
103
+ expect(res).toEqual({ foo: 'baz' });
104
+
105
+ input = { foo: { x: ['a', 'b', '${bar}'] } };
106
+ props = { bar: 'c' };
107
+ res = fillTemplate(input, props);
108
+ expect(res).toEqual({ foo: { x: ['a', 'b', 'c'] } });
109
+
110
+ input = { a: { b: { c: { d: '${x}' } } } };
111
+ props = { x: '42' };
112
+ res = fillTemplate(input, props);
113
+ expect(res).toEqual({ a: { b: { c: { d: '42' } } } });
114
+
115
+ input = { a: { b: { c: { d: ['${x}', '${y}'] } } } };
116
+ props = { x: '42', y: '23' };
117
+ res = fillTemplate(input, props);
118
+ expect(res).toEqual({ a: { b: { c: { d: ['42', '23'] } } } });
119
+
120
+ input = { foo: [{ a: 'x', b: '${bar}' }, { c: '${baz}' }] };
121
+ props = { bar: 'y', baz: 'z' };
122
+ res = fillTemplate(input, props);
123
+ expect(res).toEqual({ foo: [{ a: 'x', b: 'y' }, { c: 'z' }] });
124
+
125
+ input = { foo: { x: ['a', 'b', '${bar}'] } };
126
+ props = {};
127
+ res = fillTemplate(input, props);
128
+ expect(res).toEqual({ foo: { x: ['a', 'b', undefined] } });
129
+
130
+ input = { foo: { x: ['a', 'b', '${bar}'] } };
131
+ props = null;
132
+ res = fillTemplate(input, props);
133
+ expect(res).toEqual({ foo: { x: ['a', 'b', undefined] } });
134
+ });
135
+
136
+ test('FLOW.UTIL.3 fillTemplate', () => {
137
+ let tmplString = 'foo';
138
+ let tmplVars: any = { foo: 'bar' };
139
+ let filled = fillTemplate(tmplString, tmplVars);
140
+ expect(filled).toEqual('foo');
141
+
142
+ tmplString = '';
143
+ tmplVars = { foo: 'bar' };
144
+ filled = fillTemplate(tmplString, tmplVars);
145
+ expect(filled).toEqual('');
146
+
147
+ tmplString = null;
148
+ tmplVars = null;
149
+ filled = fillTemplate(tmplString, tmplVars);
150
+ expect(filled).toEqual(null);
151
+
152
+ tmplString = '${foo}';
153
+ tmplVars = { foo: 'bar' };
154
+ filled = fillTemplate(tmplString, tmplVars);
155
+ expect(filled).toEqual('bar');
156
+
157
+ tmplString = '4${foo}';
158
+ tmplVars = { foo: 2 };
159
+ filled = fillTemplate(tmplString, tmplVars);
160
+ expect(filled).toEqual('42');
161
+
162
+ tmplString = '${foo}';
163
+ tmplVars = { foo: 42 };
164
+ filled = fillTemplate(tmplString, tmplVars);
165
+ expect(filled).toEqual('42');
166
+
167
+ tmplString = '${foo.bar}';
168
+ tmplVars = { foo: { bar: 'baz' } };
169
+ filled = fillTemplate(tmplString, tmplVars);
170
+ expect(filled).toEqual('baz');
171
+
172
+ tmplString = '${foo}';
173
+ tmplVars = { foo: 'bar', 'a-b': 'x' };
174
+ filled = fillTemplate(tmplString, tmplVars);
175
+ expect(filled).toEqual('bar');
176
+
177
+ tmplString = '${x} ${y} c';
178
+ tmplVars = { x: 'a', y: 'b' };
179
+ filled = fillTemplate(tmplString, tmplVars);
180
+ expect(filled).toEqual('a b c');
181
+
182
+ tmplString = '${x}${y}${z}';
183
+ tmplVars = { x: 'a', z: 'c' };
184
+ filled = fillTemplate(tmplString, tmplVars);
185
+ expect(filled).toEqual('ac');
186
+
187
+ tmplString = '${foo}';
188
+ tmplVars = {};
189
+ filled = fillTemplate(tmplString, tmplVars);
190
+ expect(filled).toEqual(undefined);
191
+
192
+ tmplString = '${foo.bar.baz}';
193
+ tmplVars = {};
194
+ filled = fillTemplate(tmplString, tmplVars);
195
+ expect(filled).toEqual(undefined);
196
+
197
+ tmplString = '${foo}';
198
+ tmplVars = undefined;
199
+ filled = fillTemplate(tmplString, tmplVars);
200
+ expect(filled).toEqual(undefined);
201
+
202
+ tmplVars = null;
203
+ filled = fillTemplate(tmplString, tmplVars);
204
+ expect(filled).toEqual(undefined);
205
+
206
+ tmplString = 'foo';
207
+ filled = fillTemplate(tmplString, tmplVars);
208
+ expect(filled).toEqual('foo');
209
+ });
210
+ });