@futdevpro/nts-dynamo 1.15.1 → 1.15.3

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 (233) hide show
  1. package/.cursor/rules/__assistant_guide.mdc +30 -0
  2. package/.cursor/rules/__main.mdc +62 -0
  3. package/.cursor/rules/_ag_backend-structure.mdc +86 -0
  4. package/.cursor/rules/_ag_backend.mdc +16 -0
  5. package/.cursor/rules/_ag_debug.mdc +8 -0
  6. package/.cursor/rules/_ag_documentation_writing_rules.mdc +372 -0
  7. package/.cursor/rules/_ag_file-refactoring.mdc +113 -0
  8. package/.cursor/rules/_ag_fixes_rules.mdc +6 -0
  9. package/.cursor/rules/_ag_frontend-structure.mdc +87 -0
  10. package/.cursor/rules/_ag_frontend.mdc +40 -0
  11. package/.cursor/rules/_ag_import-rules.mdc +45 -0
  12. package/.cursor/rules/_ag_naming.mdc +116 -0
  13. package/.cursor/rules/_ag_running_commands.mdc +5 -0
  14. package/.cursor/rules/_ag_server-controller.mdc +6 -0
  15. package/.cursor/rules/_ag_should-be.mdc +7 -0
  16. package/.cursor/rules/_ag_swearing.mdc +47 -0
  17. package/.cursor/rules/ai_development_guide.md +61 -0
  18. package/.cursor/rules/ai_directives.md +114 -0
  19. package/.cursor/rules/cursor-rules.md +160 -0
  20. package/.cursor/rules/default-command.mdc +229 -0
  21. package/.cursor/rules/error_code_pattern.md +40 -0
  22. package/.cursor/rules/saved rule mcp server use.md +16 -0
  23. package/build/_models/control-models/app-params.control-model.d.ts.map +1 -1
  24. package/build/_models/control-models/app-params.control-model.js +6 -1
  25. package/build/_models/control-models/app-params.control-model.js.map +1 -1
  26. package/build/_modules/custom-data/custom-data.controller.d.ts.map +1 -1
  27. package/build/_modules/custom-data/custom-data.controller.js +1 -2
  28. package/build/_modules/custom-data/custom-data.controller.js.map +1 -1
  29. package/build/_services/core/api.service.d.ts.map +1 -1
  30. package/build/_services/core/api.service.js +1 -0
  31. package/build/_services/core/api.service.js.map +1 -1
  32. package/build/_services/server/app.server.d.ts.map +1 -1
  33. package/build/_services/server/app.server.js +11 -0
  34. package/build/_services/server/app.server.js.map +1 -1
  35. package/package.json +5 -4
  36. package/scripts/run-coverage-tests.js +5 -1
  37. package/spec/support/helpers/spec-reporter-loader.js +359 -0
  38. package/spec/support/helpers/ts-node-helper.js +84 -0
  39. package/spec/support/jasmine.coverage.json +2 -1
  40. package/spec/support/jasmine.json +3 -3
  41. package/src/_collections/archive.util.spec.ts +36 -0
  42. package/src/_collections/get-environment-settings.util.spec.ts +210 -0
  43. package/src/_collections/star.controller.spec.ts +224 -0
  44. package/src/_models/control-models/api-call-params.control-model.spec.ts +62 -3
  45. package/src/_models/control-models/app-ext-system-controls.control-model.spec.ts +52 -0
  46. package/src/_models/control-models/app-params.control-model.spec.ts +158 -2
  47. package/src/_models/control-models/app-params.control-model.ts +8 -2
  48. package/src/_models/control-models/endpoint-params.control-model.spec.ts +578 -0
  49. package/src/_modules/ai/_modules/document-ai/_collections/dai-chunking.util.spec.ts +242 -0
  50. package/src/_modules/ai/_modules/document-ai/_collections/dai-document.util.spec.ts +209 -0
  51. package/src/_modules/ai/_modules/open-ai/_services/data-services/oai-document.data-service.spec.ts +342 -0
  52. package/src/_modules/ai/_modules/open-ai/_services/data-services/oai-vector-data.service.spec.ts +550 -0
  53. package/src/_modules/ai/_modules/open-ai/_services/oai-embedding.control-service.spec.ts +240 -0
  54. package/src/_modules/ai/_modules/open-ai/_services/oai-llm-chat.service-base.spec.ts +462 -0
  55. package/src/_modules/ai/_modules/open-ai/_services/oai-llm.service-base.spec.ts +437 -0
  56. package/src/_modules/ai/_services/ai-embedding.service-base.spec.ts +98 -0
  57. package/src/_modules/ai/_services/ai-llm-chat.service-base.spec.ts +229 -0
  58. package/src/_modules/ai/_services/ai-llm.service-base.spec.ts +250 -0
  59. package/src/_modules/ai/_services/ai-provider.service-base.spec.ts +79 -0
  60. package/src/_modules/assistant/_collections/ass.util.spec.ts +176 -0
  61. package/src/_modules/assistant/_services/ass-io.control-service.spec.ts +140 -0
  62. package/src/_modules/assistant/_services/ass-main.control-service.spec.ts +192 -0
  63. package/src/_modules/bot/_modules/discord-bot/_services/dib-messaging-provider.control-service.spec.ts +431 -0
  64. package/src/_modules/bot/_modules/dynamo-bot/_collections/dyb-operations.util.spec.ts +160 -0
  65. package/src/_modules/bot/_modules/dynamo-bot/_services/dyb-messaging-provider.control-service.spec.ts +374 -0
  66. package/src/_modules/bot/_modules/slack-bot/_services/slb-messaging-provider.control-service.spec.ts +344 -0
  67. package/src/_modules/bot/_modules/teams-bot/_services/teb-messaging-provider.control-service.spec.ts +345 -0
  68. package/src/_modules/bot/_services/bot-commands.control-service.spec.ts +116 -0
  69. package/src/_modules/bot/_services/bot-io.control-service.spec.ts +285 -0
  70. package/src/_modules/bot/_services/bot-main.control-service.spec.ts +208 -0
  71. package/src/_modules/bot/_services/bot-messaging-provider.service-base.spec.ts +349 -0
  72. package/src/_modules/bot/_services/bot-routines.control-service.spec.ts +111 -0
  73. package/src/_modules/custom-data/custom-data.controller.spec.ts +49 -0
  74. package/src/_modules/custom-data/custom-data.controller.ts +1 -3
  75. package/src/_modules/custom-data/custom-data.data-service.spec.ts +54 -0
  76. package/src/_modules/custom-data/get-custom-data-routing-module.util.spec.ts +28 -0
  77. package/src/_modules/defaults/_services/default-auth.service.spec.ts +269 -0
  78. package/src/_modules/defaults/_services/default-socket-events.service.spec.ts +42 -0
  79. package/src/_modules/defaults/_services/default-user.data-service.spec.ts +187 -0
  80. package/src/_modules/discord-assistant/_collections/dias.util.spec.ts +366 -0
  81. package/src/_modules/discord-assistant/_services/dias-io.control-service.spec.ts +108 -0
  82. package/src/_modules/discord-assistant/_services/dias-main.control-service.spec.ts +22 -0
  83. package/src/_modules/discord-assistant/_services/dias.service-base.spec.ts +195 -0
  84. package/src/_modules/discord-assistant-voiced/_services/dias-discord-bot.control-service.spec.ts +34 -0
  85. package/src/_modules/discord-bot/_collections/dibo-operations.util.spec.ts +214 -0
  86. package/src/_modules/discord-bot/_services/dibo-commands.control-service.spec.ts +154 -0
  87. package/src/_modules/discord-bot/_services/dibo-io.control-service.spec.ts +264 -0
  88. package/src/_modules/discord-bot/_services/dibo-main.control-service.spec.ts +408 -0
  89. package/src/_modules/discord-bot/_services/dibo-routines.control-service.spec.ts +105 -0
  90. package/src/_modules/local-vector-search/_services/lvs-doc-chunk-data.service.spec.ts +418 -0
  91. package/src/_modules/local-vector-search/_services/lvs-local-vector-search.data-service.spec.ts +345 -0
  92. package/src/_modules/messaging/_collections/msg.util.spec.ts +226 -0
  93. package/src/_modules/messaging/_services/msg-events.service.spec.ts +219 -0
  94. package/src/_modules/messaging/_services/msg-main.control-service.spec.ts +147 -0
  95. package/src/_modules/messaging/_services/msg.controller.spec.ts +201 -0
  96. package/src/_modules/mock/data-model.mock.spec.ts +27 -24
  97. package/src/_modules/oauth2/_routes/oauth2.controller.spec.ts +107 -0
  98. package/src/_modules/oauth2/_services/oauth2.auth-service.spec.ts +254 -0
  99. package/src/_modules/oauth2/_services/oauth2.control-service.spec.ts +585 -0
  100. package/src/_modules/server/errors/errors.control-service.spec.ts +230 -0
  101. package/src/_modules/server/errors/errors.controller.spec.ts +165 -0
  102. package/src/_modules/server/errors/errors.data-service.spec.ts +355 -0
  103. package/src/_modules/server/server-status/server-status-snapshot.control-service.spec.ts +70 -0
  104. package/src/_modules/server/server-status/server-status-snapshot.data-service.spec.ts +77 -0
  105. package/src/_modules/server/server-status/server-status.control-service.spec.ts +516 -0
  106. package/src/_modules/server/server-status/server-status.controller.spec.ts +156 -0
  107. package/src/_modules/socket/_models/socket-client-service-params.control-model.spec.ts +6 -3
  108. package/src/_modules/socket/_models/socket-presence.control-model.spec.ts +164 -0
  109. package/src/_modules/socket/_services/socket-client.service.spec.ts +15 -0
  110. package/src/_modules/test/get-test-routing-module.util.spec.ts +28 -0
  111. package/src/_modules/test/test.controller.spec.ts +72 -0
  112. package/src/_modules/usage/usage.controller.spec.ts +81 -0
  113. package/src/_modules/usage/usage.data-service.spec.ts +332 -0
  114. package/src/_services/base/api.service-base.spec.ts +125 -0
  115. package/src/_services/base/archive-data.service.spec.ts +196 -0
  116. package/src/_services/base/data.service.spec.ts +493 -0
  117. package/src/_services/base/db.service.spec.ts +59 -18
  118. package/src/_services/base/singleton.service-base.spec.ts +28 -0
  119. package/src/_services/base/singleton.service.spec.ts +114 -0
  120. package/src/_services/core/api.service.ts +1 -0
  121. package/src/_services/core/auth.service.spec.ts +159 -0
  122. package/src/_services/core/email.service.spec.ts +14 -22
  123. package/src/_services/core/global.service.spec.ts +275 -0
  124. package/src/_services/core/service-collection.service.spec.ts +46 -0
  125. package/src/_services/route/routing-module.service.spec.ts +8 -6
  126. package/src/_services/server/app.server.ts +12 -0
  127. package/src/_services/shared.static-service.spec.ts +89 -0
  128. package/build/_collections/archive.util.spec.d.ts +0 -2
  129. package/build/_collections/archive.util.spec.d.ts.map +0 -1
  130. package/build/_collections/archive.util.spec.js +0 -21
  131. package/build/_collections/archive.util.spec.js.map +0 -1
  132. package/build/_models/control-models/api-call-params.control-model.spec.d.ts +0 -2
  133. package/build/_models/control-models/api-call-params.control-model.spec.d.ts.map +0 -1
  134. package/build/_models/control-models/api-call-params.control-model.spec.js +0 -80
  135. package/build/_models/control-models/api-call-params.control-model.spec.js.map +0 -1
  136. package/build/_models/control-models/app-params.control-model.spec.d.ts +0 -2
  137. package/build/_models/control-models/app-params.control-model.spec.d.ts.map +0 -1
  138. package/build/_models/control-models/app-params.control-model.spec.js +0 -59
  139. package/build/_models/control-models/app-params.control-model.spec.js.map +0 -1
  140. package/build/_models/control-models/app-system-controls.control-model.spec.d.ts +0 -2
  141. package/build/_models/control-models/app-system-controls.control-model.spec.d.ts.map +0 -1
  142. package/build/_models/control-models/app-system-controls.control-model.spec.js +0 -26
  143. package/build/_models/control-models/app-system-controls.control-model.spec.js.map +0 -1
  144. package/build/_models/control-models/http-settings.control-model.spec.d.ts +0 -2
  145. package/build/_models/control-models/http-settings.control-model.spec.d.ts.map +0 -1
  146. package/build/_models/control-models/http-settings.control-model.spec.js +0 -62
  147. package/build/_models/control-models/http-settings.control-model.spec.js.map +0 -1
  148. package/build/_models/control-models/system-control.control-model.spec.d.ts +0 -2
  149. package/build/_models/control-models/system-control.control-model.spec.d.ts.map +0 -1
  150. package/build/_models/control-models/system-control.control-model.spec.js +0 -24
  151. package/build/_models/control-models/system-control.control-model.spec.js.map +0 -1
  152. package/build/_modules/local-vector-search/_services/lvs-vector-pool.control-service.spec.d.ts +0 -2
  153. package/build/_modules/local-vector-search/_services/lvs-vector-pool.control-service.spec.d.ts.map +0 -1
  154. package/build/_modules/local-vector-search/_services/lvs-vector-pool.control-service.spec.js +0 -294
  155. package/build/_modules/local-vector-search/_services/lvs-vector-pool.control-service.spec.js.map +0 -1
  156. package/build/_modules/mock/app-params.mock.spec.d.ts +0 -2
  157. package/build/_modules/mock/app-params.mock.spec.d.ts.map +0 -1
  158. package/build/_modules/mock/app-params.mock.spec.js +0 -19
  159. package/build/_modules/mock/app-params.mock.spec.js.map +0 -1
  160. package/build/_modules/mock/auth-service.mock.spec.d.ts +0 -2
  161. package/build/_modules/mock/auth-service.mock.spec.d.ts.map +0 -1
  162. package/build/_modules/mock/auth-service.mock.spec.js +0 -40
  163. package/build/_modules/mock/auth-service.mock.spec.js.map +0 -1
  164. package/build/_modules/mock/controller.mock.spec.d.ts +0 -2
  165. package/build/_modules/mock/controller.mock.spec.d.ts.map +0 -1
  166. package/build/_modules/mock/controller.mock.spec.js +0 -23
  167. package/build/_modules/mock/controller.mock.spec.js.map +0 -1
  168. package/build/_modules/mock/data-model.mock.spec.d.ts +0 -2
  169. package/build/_modules/mock/data-model.mock.spec.d.ts.map +0 -1
  170. package/build/_modules/mock/data-model.mock.spec.js +0 -98
  171. package/build/_modules/mock/data-model.mock.spec.js.map +0 -1
  172. package/build/_modules/mock/email-service-collection.mock.spec.d.ts +0 -2
  173. package/build/_modules/mock/email-service-collection.mock.spec.d.ts.map +0 -1
  174. package/build/_modules/mock/email-service-collection.mock.spec.js +0 -21
  175. package/build/_modules/mock/email-service-collection.mock.spec.js.map +0 -1
  176. package/build/_modules/mock/email-service.mock.spec.d.ts +0 -2
  177. package/build/_modules/mock/email-service.mock.spec.d.ts.map +0 -1
  178. package/build/_modules/mock/email-service.mock.spec.js +0 -14
  179. package/build/_modules/mock/email-service.mock.spec.js.map +0 -1
  180. package/build/_modules/mock/socket-client.mock.spec.d.ts +0 -2
  181. package/build/_modules/mock/socket-client.mock.spec.d.ts.map +0 -1
  182. package/build/_modules/mock/socket-client.mock.spec.js +0 -32
  183. package/build/_modules/mock/socket-client.mock.spec.js.map +0 -1
  184. package/build/_modules/mock/socket-server.mock.spec.d.ts +0 -2
  185. package/build/_modules/mock/socket-server.mock.spec.d.ts.map +0 -1
  186. package/build/_modules/mock/socket-server.mock.spec.js +0 -36
  187. package/build/_modules/mock/socket-server.mock.spec.js.map +0 -1
  188. package/build/_modules/socket/_models/socket-client-service-params.control-model.spec.d.ts +0 -2
  189. package/build/_modules/socket/_models/socket-client-service-params.control-model.spec.d.ts.map +0 -1
  190. package/build/_modules/socket/_models/socket-client-service-params.control-model.spec.js +0 -22
  191. package/build/_modules/socket/_models/socket-client-service-params.control-model.spec.js.map +0 -1
  192. package/build/_modules/socket/_models/socket-server-service-params.control-model.spec.d.ts +0 -2
  193. package/build/_modules/socket/_models/socket-server-service-params.control-model.spec.d.ts.map +0 -1
  194. package/build/_modules/socket/_models/socket-server-service-params.control-model.spec.js +0 -29
  195. package/build/_modules/socket/_models/socket-server-service-params.control-model.spec.js.map +0 -1
  196. package/build/_modules/socket/_services/socket-server.service.spec.d.ts +0 -2
  197. package/build/_modules/socket/_services/socket-server.service.spec.d.ts.map +0 -1
  198. package/build/_modules/socket/_services/socket-server.service.spec.js +0 -9
  199. package/build/_modules/socket/_services/socket-server.service.spec.js.map +0 -1
  200. package/build/_modules/socket/app-extended.server.spec.d.ts +0 -2
  201. package/build/_modules/socket/app-extended.server.spec.d.ts.map +0 -1
  202. package/build/_modules/socket/app-extended.server.spec.js +0 -149
  203. package/build/_modules/socket/app-extended.server.spec.js.map +0 -1
  204. package/build/_services/base/db.service.spec.d.ts +0 -1
  205. package/build/_services/base/db.service.spec.d.ts.map +0 -1
  206. package/build/_services/base/db.service.spec.js +0 -28
  207. package/build/_services/base/db.service.spec.js.map +0 -1
  208. package/build/_services/core/api.service.spec.d.ts +0 -2
  209. package/build/_services/core/api.service.spec.d.ts.map +0 -1
  210. package/build/_services/core/api.service.spec.js +0 -109
  211. package/build/_services/core/api.service.spec.js.map +0 -1
  212. package/build/_services/core/email.service.spec.d.ts +0 -2
  213. package/build/_services/core/email.service.spec.d.ts.map +0 -1
  214. package/build/_services/core/email.service.spec.js +0 -77
  215. package/build/_services/core/email.service.spec.js.map +0 -1
  216. package/build/_services/route/controller.service.spec.d.ts +0 -2
  217. package/build/_services/route/controller.service.spec.d.ts.map +0 -1
  218. package/build/_services/route/controller.service.spec.js +0 -48
  219. package/build/_services/route/controller.service.spec.js.map +0 -1
  220. package/build/_services/route/routing-module.service.spec.d.ts +0 -2
  221. package/build/_services/route/routing-module.service.spec.d.ts.map +0 -1
  222. package/build/_services/route/routing-module.service.spec.js +0 -64
  223. package/build/_services/route/routing-module.service.spec.js.map +0 -1
  224. package/build/_services/server/app.server.spec.d.ts +0 -2
  225. package/build/_services/server/app.server.spec.d.ts.map +0 -1
  226. package/build/_services/server/app.server.spec.js +0 -80
  227. package/build/_services/server/app.server.spec.js.map +0 -1
  228. package/build/_services/shared.static-service.spec.d.ts +0 -2
  229. package/build/_services/shared.static-service.spec.d.ts.map +0 -1
  230. package/build/_services/shared.static-service.spec.js +0 -12
  231. package/build/_services/shared.static-service.spec.js.map +0 -1
  232. package/src/_modules/socket/app-extended.server.spec.ts +0 -227
  233. package/src/_services/server/app.server.spec.ts +0 -138
@@ -0,0 +1,240 @@
1
+
2
+ import { DyNTS_OAI_Embedding_ControlService } from './oai-embedding.control-service';
3
+ import { DyFM_OAI_Settings, DyFM_OAI_Model } from '@futdevpro/fsm-dynamo/ai/open-ai';
4
+ import { DyFM_Error } from '@futdevpro/fsm-dynamo';
5
+ import { CreateEmbeddingResponse } from 'openai/resources';
6
+
7
+ describe('| DyNTS_OAI_Embedding_ControlService', () => {
8
+ let service: DyNTS_OAI_Embedding_ControlService;
9
+ let mockOpenAI: any;
10
+ let mockEmbeddingsCreate: jasmine.Spy;
11
+
12
+ const mockSettings: DyFM_OAI_Settings = {
13
+ config: {
14
+ apiKey: 'test-api-key',
15
+ organization: 'test-org',
16
+ },
17
+ defaultSettings: {
18
+ useModel: DyFM_OAI_Model.textEmbedding_3Small,
19
+ },
20
+ };
21
+
22
+ beforeEach(() => {
23
+ mockEmbeddingsCreate = jasmine.createSpy('embeddings.create').and.returnValue({
24
+ data: [
25
+ {
26
+ embedding: [0.1, 0.2, 0.3],
27
+ index: 0,
28
+ object: 'embedding',
29
+ },
30
+ ],
31
+ model: 'text-embedding-3-small',
32
+ object: 'list',
33
+ usage: {
34
+ prompt_tokens: 5,
35
+ total_tokens: 5,
36
+ },
37
+ });
38
+
39
+ mockOpenAI = {
40
+ embeddings: {
41
+ create: mockEmbeddingsCreate,
42
+ },
43
+ };
44
+
45
+ service = new DyNTS_OAI_Embedding_ControlService(mockSettings);
46
+ (service as any).openai = mockOpenAI;
47
+ });
48
+
49
+ describe('| createEmbedding', () => {
50
+ it('| should create embedding for single text', async () => {
51
+ const result = await service.createEmbedding({
52
+ text: 'Test text',
53
+ model: DyFM_OAI_Model.textEmbedding_3Small,
54
+ issuer: 'test-issuer',
55
+ });
56
+
57
+ expect(result).toEqual([0.1, 0.2, 0.3]);
58
+ expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
59
+ model: DyFM_OAI_Model.textEmbedding_3Small,
60
+ input: 'Test text',
61
+ });
62
+ });
63
+
64
+ it('| should return full response when fullResponse is true', async () => {
65
+ const mockResponse: CreateEmbeddingResponse = {
66
+ data: [
67
+ {
68
+ embedding: [0.1, 0.2, 0.3],
69
+ index: 0,
70
+ object: 'embedding',
71
+ },
72
+ ],
73
+ model: 'text-embedding-3-small',
74
+ object: 'list',
75
+ usage: {
76
+ prompt_tokens: 5,
77
+ total_tokens: 5,
78
+ },
79
+ };
80
+
81
+ mockEmbeddingsCreate.and.returnValue(mockResponse);
82
+
83
+ const result = await service.createEmbedding({
84
+ text: 'Test text',
85
+ model: DyFM_OAI_Model.textEmbedding_3Small,
86
+ fullResponse: true,
87
+ issuer: 'test-issuer',
88
+ });
89
+
90
+ expect(result).toEqual(mockResponse);
91
+ });
92
+
93
+ it('| should throw error when text is missing', async () => {
94
+ try {
95
+ await service.createEmbedding({
96
+ text: '',
97
+ model: DyFM_OAI_Model.textEmbedding_3Small,
98
+ issuer: 'test-issuer',
99
+ });
100
+ fail('Should have thrown an error');
101
+ } catch (error) {
102
+ expect(error).toBeInstanceOf(DyFM_Error);
103
+ expect((error as DyFM_Error)._errorCode).toContain('DyNTS-ECS-CE1');
104
+ }
105
+ });
106
+
107
+ it('| should handle API errors', async () => {
108
+ const apiError = new Error('API Error');
109
+ mockEmbeddingsCreate.and.throwError(apiError);
110
+
111
+ try {
112
+ await service.createEmbedding({
113
+ text: 'Test text',
114
+ model: DyFM_OAI_Model.textEmbedding_3Small,
115
+ issuer: 'test-issuer',
116
+ });
117
+ fail('Should have thrown an error');
118
+ } catch (error) {
119
+ expect(error).toBeInstanceOf(DyFM_Error);
120
+ expect((error as DyFM_Error)._errorCode).toContain('DyNTS-ECS-CE0');
121
+ }
122
+ });
123
+ });
124
+
125
+ describe('| createEmbeddings', () => {
126
+ beforeEach(() => {
127
+ mockEmbeddingsCreate.and.returnValue({
128
+ data: [
129
+ {
130
+ embedding: [0.1, 0.2, 0.3],
131
+ index: 0,
132
+ object: 'embedding',
133
+ },
134
+ {
135
+ embedding: [0.4, 0.5, 0.6],
136
+ index: 1,
137
+ object: 'embedding',
138
+ },
139
+ ],
140
+ model: 'text-embedding-3-small',
141
+ object: 'list',
142
+ usage: {
143
+ prompt_tokens: 10,
144
+ total_tokens: 10,
145
+ },
146
+ });
147
+ });
148
+
149
+ it('| should create embeddings for multiple texts', async () => {
150
+ const result = await service.createEmbeddings({
151
+ texts: ['Text 1', 'Text 2'],
152
+ model: DyFM_OAI_Model.textEmbedding_3Small,
153
+ issuer: 'test-issuer',
154
+ });
155
+
156
+ expect(result).toEqual([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]);
157
+ expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
158
+ model: DyFM_OAI_Model.textEmbedding_3Small,
159
+ input: ['Text 1', 'Text 2'],
160
+ });
161
+ });
162
+
163
+ it('| should return full response when fullResponse is true', async () => {
164
+ const mockResponse: CreateEmbeddingResponse = {
165
+ data: [
166
+ {
167
+ embedding: [0.1, 0.2, 0.3],
168
+ index: 0,
169
+ object: 'embedding',
170
+ },
171
+ ],
172
+ model: 'text-embedding-3-small',
173
+ object: 'list',
174
+ usage: {
175
+ prompt_tokens: 5,
176
+ total_tokens: 5,
177
+ },
178
+ };
179
+
180
+ mockEmbeddingsCreate.and.returnValue(mockResponse);
181
+
182
+ const result = await service.createEmbeddings({
183
+ texts: ['Text 1'],
184
+ model: DyFM_OAI_Model.textEmbedding_3Small,
185
+ fullResponse: true,
186
+ issuer: 'test-issuer',
187
+ });
188
+
189
+ expect(result).toEqual(mockResponse);
190
+ });
191
+
192
+ it('| should throw error when texts is missing', async () => {
193
+ try {
194
+ await service.createEmbeddings({
195
+ texts: null as any,
196
+ model: DyFM_OAI_Model.textEmbedding_3Small,
197
+ issuer: 'test-issuer',
198
+ });
199
+ fail('Should have thrown an error');
200
+ } catch (error) {
201
+ expect(error).toBeInstanceOf(DyFM_Error);
202
+ expect((error as DyFM_Error)._errorCode).toContain('DyNTS-ECS-CES1');
203
+ }
204
+ });
205
+
206
+ it('| should handle API errors', async () => {
207
+ const apiError = new Error('API Error');
208
+ mockEmbeddingsCreate.and.throwError(apiError);
209
+
210
+ try {
211
+ await service.createEmbeddings({
212
+ texts: ['Text 1'],
213
+ model: DyFM_OAI_Model.textEmbedding_3Small,
214
+ issuer: 'test-issuer',
215
+ });
216
+ fail('Should have thrown an error');
217
+ } catch (error) {
218
+ expect(error).toBeInstanceOf(DyFM_Error);
219
+ expect((error as DyFM_Error)._errorCode).toContain('DyNTS-ECS-CES0');
220
+ }
221
+ });
222
+ });
223
+
224
+ describe('| getEmbeddingInfo', () => {
225
+ it('| should return embedding info for model', () => {
226
+ const result = service.getEmbeddingInfo('text-embedding-3-small');
227
+
228
+ expect(result.provider).toBeDefined();
229
+ expect(result.model).toBe('text-embedding-3-small');
230
+ });
231
+
232
+ it('| should return embedding info for different models', () => {
233
+ const result = service.getEmbeddingInfo('text-embedding-3-large');
234
+
235
+ expect(result.provider).toBeDefined();
236
+ expect(result.model).toBe('text-embedding-3-large');
237
+ });
238
+ });
239
+ });
240
+
@@ -0,0 +1,462 @@
1
+
2
+ import { DyNTS_OAI_LLMChat_ServiceBase } from './oai-llm-chat.service-base';
3
+ import { DyFM_OAI_Settings, DyFM_OAI_Model, DyFM_OAI_CallSettings } from '@futdevpro/fsm-dynamo/ai/open-ai';
4
+ import { DyFM_AI_MessageRole, DyFM_AI_Message } from '@futdevpro/fsm-dynamo/ai';
5
+ import { DyFM_Error, DyFM_Log, DyFM_Object, DyFM_Array } from '@futdevpro/fsm-dynamo';
6
+ import { ChatCompletion } from 'openai/resources';
7
+
8
+ describe('| DyNTS_OAI_LLMChat_ServiceBase', () => {
9
+ let service: DyNTS_OAI_LLMChat_ServiceBase;
10
+ let mockOpenAI: any;
11
+ let mockChatCompletionsCreate: jasmine.Spy;
12
+
13
+ const mockSettings: DyFM_OAI_Settings = {
14
+ config: {
15
+ apiKey: 'test-api-key',
16
+ organization: 'test-org',
17
+ },
18
+ defaultSettings: {
19
+ useModel: DyFM_OAI_Model.gpt4o_mini,
20
+ systemPrompt: 'Test system prompt',
21
+ temperature: 0.7,
22
+ maxTokens: 1000,
23
+ },
24
+ };
25
+
26
+ beforeEach(() => {
27
+ mockChatCompletionsCreate = jasmine.createSpy('chat.completions.create').and.returnValue({
28
+ id: 'chatcmpl-123',
29
+ object: 'chat.completion',
30
+ created: 1234567890,
31
+ model: 'gpt-4o-mini',
32
+ choices: [
33
+ {
34
+ index: 0,
35
+ message: {
36
+ role: 'assistant',
37
+ content: 'Test response',
38
+ },
39
+ finish_reason: 'stop',
40
+ },
41
+ ],
42
+ usage: {
43
+ prompt_tokens: 10,
44
+ completion_tokens: 5,
45
+ total_tokens: 15,
46
+ },
47
+ });
48
+
49
+ mockOpenAI = {
50
+ chat: {
51
+ completions: {
52
+ create: mockChatCompletionsCreate,
53
+ },
54
+ },
55
+ };
56
+
57
+ service = new DyNTS_OAI_LLMChat_ServiceBase(mockSettings);
58
+ (service as any).openai = mockOpenAI;
59
+ });
60
+
61
+ describe('| requestYesNoInConversation', () => {
62
+ it('| should return boolean from conversation', async () => {
63
+ const conversation: DyFM_AI_Message[] = [];
64
+ spyOn(service, 'requestSimpleMessageInConversation').and.returnValue(Promise.resolve('YES'));
65
+ spyOn(service as any, 'convertAnswerToBoolean').and.returnValue(true);
66
+
67
+ const result = await service.requestYesNoInConversation({
68
+ message: 'Is this a test?',
69
+ conversation: conversation,
70
+ issuer: 'test-issuer',
71
+ });
72
+
73
+ expect(result).toBe(true);
74
+ expect((service as any).convertAnswerToBoolean).toHaveBeenCalled();
75
+ });
76
+
77
+ it('| should append predefined request to message', async () => {
78
+ const conversation: DyFM_AI_Message[] = [];
79
+ spyOn(service, 'requestSimpleMessageInConversation').and.returnValue(Promise.resolve('YES'));
80
+
81
+ await service.requestYesNoInConversation({
82
+ message: 'Is this a test?',
83
+ conversation: conversation,
84
+ issuer: 'test-issuer',
85
+ });
86
+
87
+ expect(service.requestSimpleMessageInConversation).toHaveBeenCalled();
88
+ });
89
+ });
90
+
91
+ describe('| requestPercentageInConversation', () => {
92
+ it('| should return number from conversation', async () => {
93
+ const conversation: DyFM_AI_Message[] = [];
94
+ spyOn(service, 'requestSimpleMessageInConversation').and.returnValue(Promise.resolve('75'));
95
+ spyOn(service as any, 'convertAnswerToNumber').and.returnValue(75);
96
+
97
+ const result = await service.requestPercentageInConversation({
98
+ message: 'What percentage?',
99
+ conversation: conversation,
100
+ issuer: 'test-issuer',
101
+ });
102
+
103
+ expect(result).toBe(75);
104
+ expect((service as any).convertAnswerToNumber).toHaveBeenCalled();
105
+ });
106
+ });
107
+
108
+ describe('| requestSelectInConversation', () => {
109
+ it('| should return selected option from conversation', async () => {
110
+ const conversation: DyFM_AI_Message[] = [];
111
+ const options = ['option1', 'option2', 'option3'];
112
+ spyOn(service, 'requestSimpleMessageInConversation').and.returnValue(Promise.resolve('option1'));
113
+ spyOn(service as any, 'stringifySelectOptions').and.returnValue(['option1', 'option2', 'option3']);
114
+ spyOn(service as any, 'getTextListAsText').and.returnValue('option1, option2, option3');
115
+ spyOn(service as any, 'convertAnswerToSelectOption').and.returnValue('option1');
116
+
117
+ const result = await service.requestSelectInConversation({
118
+ message: 'Select an option',
119
+ conversation: conversation,
120
+ selectFrom: options,
121
+ issuer: 'test-issuer',
122
+ });
123
+
124
+ expect(result).toBe('option1');
125
+ });
126
+ });
127
+
128
+ describe('| requestMultiselectInConversation', () => {
129
+ it('| should return multiple selected options from conversation', async () => {
130
+ const conversation: DyFM_AI_Message[] = [];
131
+ const options = ['option1', 'option2', 'option3'];
132
+ spyOn(service, 'requestSimpleMessageInConversation').and.returnValue(Promise.resolve('option1 and option2'));
133
+ spyOn(service as any, 'stringifySelectOptions').and.returnValue(['option1', 'option2', 'option3']);
134
+ spyOn(service as any, 'getTextListAsText').and.returnValue('option1, option2, option3');
135
+ spyOn(service as any, 'convertAnswerToList').and.returnValue(['option1', 'option2']);
136
+
137
+ const result = await service.requestMultiselectInConversation({
138
+ message: 'Select options',
139
+ conversation: conversation,
140
+ options: options,
141
+ issuer: 'test-issuer',
142
+ });
143
+
144
+ expect(result).toEqual(['option1', 'option2']);
145
+ });
146
+ });
147
+
148
+ describe('| requestJSONInConversation', () => {
149
+ it('| should return parsed JSON from conversation', async () => {
150
+ const conversation: DyFM_AI_Message[] = [];
151
+ const jsonString = '{"key": "value"}';
152
+ spyOn(service, 'requestSimpleMessageInConversation').and.returnValue(Promise.resolve(jsonString));
153
+ spyOn(service as any, 'convertAnswerToJSON').and.returnValue({ key: 'value' });
154
+
155
+ const result = await service.requestJSONInConversation<{ key: string }>({
156
+ message: 'Return JSON',
157
+ conversation: conversation,
158
+ issuer: 'test-issuer',
159
+ });
160
+
161
+ expect(result).toEqual({ key: 'value' });
162
+ });
163
+ });
164
+
165
+ describe('| requestJSONInConversationWithKeysDescription', () => {
166
+ it('| should return parsed JSON with keys description', async () => {
167
+ const conversation: DyFM_AI_Message[] = [];
168
+ const jsonString = '{"key": "value"}';
169
+ spyOn(service, 'requestSimpleMessageInConversation').and.returnValue(Promise.resolve(jsonString));
170
+ spyOn(service as any, 'convertAnswerToJSON').and.returnValue({ key: 'value' });
171
+
172
+ const result = await service.requestJSONInConversationWithKeysDescription<{ key: string }>({
173
+ message: 'Return JSON',
174
+ conversation: conversation,
175
+ keysDescription: 'key: string',
176
+ issuer: 'test-issuer',
177
+ });
178
+
179
+ expect(result).toEqual({ key: 'value' });
180
+ });
181
+ });
182
+
183
+ describe('| requestJSONInConversationWithExactKeys', () => {
184
+ it('| should return parsed JSON with exact keys', async () => {
185
+ const conversation: DyFM_AI_Message[] = [];
186
+ const jsonString = '{"key1": "value1", "key2": "value2"}';
187
+ spyOn(service, 'requestSimpleMessageInConversation').and.returnValue(Promise.resolve(jsonString));
188
+ spyOn(service as any, 'getTextListAsText').and.returnValue('key1, key2');
189
+ spyOn(service as any, 'convertAnswerToJSON').and.returnValue({ key1: 'value1', key2: 'value2' });
190
+
191
+ const result = await service.requestJSONInConversationWithExactKeys<{ key1: string; key2: string }>({
192
+ message: 'Return JSON',
193
+ conversation: conversation,
194
+ keys: ['key1', 'key2'],
195
+ issuer: 'test-issuer',
196
+ });
197
+
198
+ expect(result).toEqual({ key1: 'value1', key2: 'value2' });
199
+ });
200
+ });
201
+
202
+ describe('| requestListInConversation', () => {
203
+ it('| should return list from conversation', async () => {
204
+ const conversation: DyFM_AI_Message[] = [];
205
+ spyOn(service, 'requestSimpleMessageInConversation').and.returnValue(Promise.resolve('item1, item2, item3'));
206
+ spyOn(service as any, 'convertAnswerToList').and.returnValue(['item1', 'item2', 'item3']);
207
+
208
+ const result = await service.requestListInConversation<string>({
209
+ message: 'Return a list',
210
+ conversation: conversation,
211
+ issuer: 'test-issuer',
212
+ });
213
+
214
+ expect(result).toEqual(['item1', 'item2', 'item3']);
215
+ });
216
+ });
217
+
218
+ describe('| requestSimpleMessageInConversation', () => {
219
+ it('| should resolve conversation and return answer', async () => {
220
+ const conversation: DyFM_AI_Message[] = [];
221
+ spyOn(service as any, 'logAnswer');
222
+ spyOn(service, 'resolveConversation').and.returnValue(Promise.resolve('Test response'));
223
+
224
+ const result = await service.requestSimpleMessageInConversation({
225
+ message: 'Test question',
226
+ conversation: conversation,
227
+ issuer: 'test-issuer',
228
+ });
229
+
230
+ expect(result).toBe('Test response');
231
+ expect((service as any).logAnswer).toHaveBeenCalled();
232
+ });
233
+
234
+ it('| should add user message to conversation', async () => {
235
+ const conversation: DyFM_AI_Message[] = [];
236
+ spyOn(service, 'resolveConversation').and.returnValue(Promise.resolve('Test response'));
237
+
238
+ await service.requestSimpleMessageInConversation({
239
+ message: 'Test question',
240
+ conversation: conversation,
241
+ issuer: 'test-issuer',
242
+ });
243
+
244
+ expect(conversation.length).toBe(1);
245
+ expect(conversation[0].role).toBe(DyFM_AI_MessageRole.user);
246
+ expect(conversation[0].content).toBe('Test question');
247
+ });
248
+ });
249
+
250
+ describe('| resolveConversation', () => {
251
+ it('| should return string answer when getFullResponse is false', async () => {
252
+ const conversation: DyFM_AI_Message[] = [
253
+ { role: DyFM_AI_MessageRole.user, content: 'Test question' },
254
+ ];
255
+
256
+ spyOn(service as any, 'getDefaultSystemMessage').and.returnValue({
257
+ role: DyFM_AI_MessageRole.system,
258
+ content: 'System prompt',
259
+ });
260
+ spyOn(service as any, 'shortenConversation').and.returnValue(conversation);
261
+ spyOn(service as any, 'validateConversation');
262
+ spyOn(service as any, 'logConversation');
263
+ spyOn(service as any, 'getMessageCreateInput').and.returnValue({
264
+ model: DyFM_OAI_Model.gpt4o_mini,
265
+ messages: conversation as any,
266
+ });
267
+
268
+ const result = await service.resolveConversation({
269
+ conversation: conversation,
270
+ issuer: 'test-issuer',
271
+ getFullResponse: false,
272
+ });
273
+
274
+ expect(result).toBe('Test response');
275
+ expect(mockChatCompletionsCreate).toHaveBeenCalled();
276
+ });
277
+
278
+ it('| should return full ChatCompletion when getFullResponse is true', async () => {
279
+ const conversation: DyFM_AI_Message[] = [
280
+ { role: DyFM_AI_MessageRole.user, content: 'Test question' },
281
+ ];
282
+
283
+ const fullResponse: ChatCompletion = {
284
+ id: 'chatcmpl-123',
285
+ object: 'chat.completion',
286
+ created: 1234567890,
287
+ model: 'gpt-4o-mini',
288
+ choices: [
289
+ {
290
+ index: 0,
291
+ message: {
292
+ role: 'assistant',
293
+ content: 'Test response',
294
+ refusal: null,
295
+ },
296
+ logprobs: null,
297
+ finish_reason: 'stop',
298
+ },
299
+ ],
300
+ usage: {
301
+ prompt_tokens: 10,
302
+ completion_tokens: 5,
303
+ total_tokens: 15,
304
+ },
305
+ };
306
+
307
+ mockChatCompletionsCreate.and.returnValue(fullResponse);
308
+ spyOn(service as any, 'getDefaultSystemMessage').and.returnValue({
309
+ role: DyFM_AI_MessageRole.system,
310
+ content: 'System prompt',
311
+ });
312
+ spyOn(service as any, 'shortenConversation').and.returnValue(conversation);
313
+ spyOn(service as any, 'validateConversation');
314
+ spyOn(service as any, 'logConversation');
315
+ spyOn(service as any, 'getMessageCreateInput').and.returnValue({
316
+ model: DyFM_OAI_Model.gpt4o_mini,
317
+ messages: conversation as any,
318
+ });
319
+
320
+ const result = await service.resolveConversation({
321
+ conversation: conversation,
322
+ issuer: 'test-issuer',
323
+ getFullResponse: true,
324
+ });
325
+
326
+ expect(result).toEqual(fullResponse);
327
+ });
328
+
329
+ it('| should add system message if not present', async () => {
330
+ const conversation: DyFM_AI_Message[] = [
331
+ { role: DyFM_AI_MessageRole.user, content: 'Test question' },
332
+ ];
333
+
334
+ spyOn(service as any, 'getDefaultSystemMessage').and.returnValue({
335
+ role: DyFM_AI_MessageRole.system,
336
+ content: 'System prompt',
337
+ });
338
+ spyOn(service as any, 'shortenConversation').and.returnValue(conversation);
339
+ spyOn(service as any, 'validateConversation');
340
+ spyOn(service as any, 'logConversation');
341
+ spyOn(service as any, 'getMessageCreateInput').and.returnValue({
342
+ model: DyFM_OAI_Model.gpt4o_mini,
343
+ messages: conversation as any,
344
+ });
345
+
346
+ await service.resolveConversation({
347
+ conversation: conversation,
348
+ issuer: 'test-issuer',
349
+ });
350
+
351
+ expect((service as any).getDefaultSystemMessage).toHaveBeenCalled();
352
+ });
353
+
354
+ it('| should handle API errors', async () => {
355
+ const conversation: DyFM_AI_Message[] = [
356
+ { role: DyFM_AI_MessageRole.user, content: 'Test question' },
357
+ ];
358
+
359
+ mockChatCompletionsCreate.and.throwError(new Error('API Error'));
360
+ spyOn(service as any, 'getDefaultSystemMessage').and.returnValue({
361
+ role: DyFM_AI_MessageRole.system,
362
+ content: 'System prompt',
363
+ });
364
+ spyOn(service as any, 'shortenConversation').and.returnValue(conversation);
365
+ spyOn(service as any, 'validateConversation');
366
+ spyOn(service as any, 'logConversation');
367
+ spyOn(service as any, 'getMessageCreateInput').and.returnValue({
368
+ model: DyFM_OAI_Model.gpt4o_mini,
369
+ messages: conversation as any,
370
+ });
371
+
372
+ try {
373
+ await service.resolveConversation({
374
+ conversation: conversation,
375
+ issuer: 'test-issuer',
376
+ });
377
+ fail('Should have thrown an error');
378
+ } catch (error) {
379
+ expect(error).toBeInstanceOf(DyFM_Error);
380
+ expect((error as DyFM_Error)._errorCode).toContain('DyNTS-OAB-RC0');
381
+ }
382
+ });
383
+ });
384
+
385
+ describe('| shortenConversation', () => {
386
+ it('| should return conversation when under limit', () => {
387
+ const conversation: DyFM_AI_Message[] = [
388
+ { role: DyFM_AI_MessageRole.user, content: 'Short message' },
389
+ ];
390
+
391
+ spyOn(DyFM_Object, 'clone').and.returnValue(conversation);
392
+ spyOn(DyFM_Array, 'last').and.returnValue(conversation[0]);
393
+
394
+ const result = (service as any).shortenConversation({
395
+ conversation: conversation,
396
+ issuer: 'test-issuer',
397
+ });
398
+
399
+ expect(result).toBeDefined();
400
+ });
401
+
402
+ it('| should shorten conversation when over limit', () => {
403
+ const longMessage = 'x'.repeat(10000);
404
+ const conversation: DyFM_AI_Message[] = Array(100).fill(null).map((_, i) => ({
405
+ role: DyFM_AI_MessageRole.user,
406
+ content: `Message ${i}: ${longMessage}`,
407
+ }));
408
+
409
+ const clonedConversation = [...conversation];
410
+ spyOn(DyFM_Object, 'clone').and.returnValue(clonedConversation);
411
+ spyOn(DyFM_Array, 'last').and.returnValue(conversation[conversation.length - 1]);
412
+ spyOn(DyFM_Log, 'info');
413
+
414
+ const result = (service as any).shortenConversation({
415
+ conversation: conversation,
416
+ issuer: 'test-issuer',
417
+ });
418
+
419
+ expect(result).toBeDefined();
420
+ expect(result.length).toBeLessThanOrEqual(conversation.length);
421
+ });
422
+
423
+ it('| should preserve system messages', () => {
424
+ const conversation: DyFM_AI_Message[] = [
425
+ { role: DyFM_AI_MessageRole.system, content: 'System prompt' },
426
+ { role: DyFM_AI_MessageRole.user, content: 'User message' },
427
+ ];
428
+
429
+ const clonedConversation = [...conversation];
430
+ spyOn(DyFM_Object, 'clone').and.returnValue(clonedConversation);
431
+ spyOn(DyFM_Array, 'last').and.returnValue(conversation[1]);
432
+
433
+ const result = (service as any).shortenConversation({
434
+ conversation: conversation,
435
+ issuer: 'test-issuer',
436
+ });
437
+
438
+ const systemMessages = result.filter(m => m.role === DyFM_AI_MessageRole.system);
439
+ expect(systemMessages.length).toBeGreaterThan(0);
440
+ });
441
+
442
+ it('| should throw error when no messages left', () => {
443
+ const conversation: DyFM_AI_Message[] = [];
444
+
445
+ const clonedConversation: DyFM_AI_Message[] = [];
446
+ spyOn(DyFM_Object, 'clone').and.returnValue(clonedConversation);
447
+ spyOn(DyFM_Log, 'error');
448
+
449
+ try {
450
+ (service as any).shortenConversation({
451
+ conversation: conversation,
452
+ issuer: 'test-issuer',
453
+ });
454
+ fail('Should have thrown an error');
455
+ } catch (error) {
456
+ expect(error).toBeInstanceOf(DyFM_Error);
457
+ expect((error as DyFM_Error)._errorCode).toContain('DyNTS-OAB-SC0');
458
+ }
459
+ });
460
+ });
461
+ });
462
+