@slingr/cli 0.0.3 → 0.0.4

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 (251) hide show
  1. package/LICENSE.txt +202 -0
  2. package/README.md +490 -319
  3. package/bin/dev.cmd +2 -2
  4. package/bin/dev.js +5 -5
  5. package/bin/run.cmd +2 -2
  6. package/bin/run.js +4 -4
  7. package/bin/slingr +1 -0
  8. package/dist/commands/build.d.ts +20 -0
  9. package/dist/commands/build.d.ts.map +1 -0
  10. package/dist/commands/build.js +206 -0
  11. package/dist/commands/build.js.map +1 -0
  12. package/dist/commands/create-app.d.ts +0 -1
  13. package/dist/commands/create-app.d.ts.map +1 -1
  14. package/dist/commands/create-app.js +38 -57
  15. package/dist/commands/create-app.js.map +1 -1
  16. package/dist/commands/debug.d.ts +28 -0
  17. package/dist/commands/debug.d.ts.map +1 -0
  18. package/dist/commands/debug.js +474 -0
  19. package/dist/commands/debug.js.map +1 -0
  20. package/dist/commands/ds.d.ts +14 -1
  21. package/dist/commands/ds.d.ts.map +1 -1
  22. package/dist/commands/ds.js +450 -121
  23. package/dist/commands/ds.js.map +1 -1
  24. package/dist/commands/gql.d.ts +1 -1
  25. package/dist/commands/gql.d.ts.map +1 -1
  26. package/dist/commands/gql.js +190 -184
  27. package/dist/commands/gql.js.map +1 -1
  28. package/dist/commands/infra/down.d.ts.map +1 -1
  29. package/dist/commands/infra/down.js +8 -7
  30. package/dist/commands/infra/down.js.map +1 -1
  31. package/dist/commands/infra/up.d.ts.map +1 -1
  32. package/dist/commands/infra/up.js +8 -7
  33. package/dist/commands/infra/up.js.map +1 -1
  34. package/dist/commands/infra/update.d.ts +1 -0
  35. package/dist/commands/infra/update.d.ts.map +1 -1
  36. package/dist/commands/infra/update.js +33 -69
  37. package/dist/commands/infra/update.js.map +1 -1
  38. package/dist/commands/run.d.ts +29 -2
  39. package/dist/commands/run.d.ts.map +1 -1
  40. package/dist/commands/run.js +628 -130
  41. package/dist/commands/run.js.map +1 -1
  42. package/dist/commands/setup.d.ts +1 -1
  43. package/dist/commands/setup.d.ts.map +1 -1
  44. package/dist/commands/setup.js +34 -71
  45. package/dist/commands/setup.js.map +1 -1
  46. package/dist/commands/sync-metadata.d.ts +15 -0
  47. package/dist/commands/sync-metadata.d.ts.map +1 -0
  48. package/dist/commands/sync-metadata.js +225 -0
  49. package/dist/commands/sync-metadata.js.map +1 -0
  50. package/dist/commands/users.d.ts +30 -0
  51. package/dist/commands/users.d.ts.map +1 -0
  52. package/dist/commands/users.js +472 -0
  53. package/dist/commands/users.js.map +1 -0
  54. package/dist/commands/views.d.ts +11 -0
  55. package/dist/commands/views.d.ts.map +1 -0
  56. package/dist/commands/views.js +73 -0
  57. package/dist/commands/views.js.map +1 -0
  58. package/dist/projectStructure.d.ts +2 -2
  59. package/dist/projectStructure.d.ts.map +1 -1
  60. package/dist/projectStructure.js +281 -69
  61. package/dist/projectStructure.js.map +1 -1
  62. package/dist/scripts/generate-metadata.d.ts +13 -0
  63. package/dist/scripts/generate-metadata.d.ts.map +1 -0
  64. package/dist/scripts/generate-metadata.js +412 -0
  65. package/dist/scripts/generate-metadata.js.map +1 -0
  66. package/dist/scripts/generate-metadata.ts +498 -0
  67. package/dist/scripts/generate-schema.d.ts +1 -1
  68. package/dist/scripts/generate-schema.js +168 -74
  69. package/dist/scripts/generate-schema.js.map +1 -1
  70. package/dist/scripts/generate-schema.ts +258 -143
  71. package/dist/templates/.env.template +23 -0
  72. package/dist/templates/.firebaserc.template +5 -0
  73. package/dist/templates/.github/copilot-instructions.md.template +652 -17
  74. package/dist/templates/backend/Dockerfile.template +30 -0
  75. package/dist/templates/config/datasource.ts.template +12 -9
  76. package/dist/templates/config/jest.config.ts +30 -30
  77. package/dist/templates/config/jest.setup.ts +1 -1
  78. package/dist/templates/config/tsconfig.json.template +50 -29
  79. package/dist/templates/dataSources/mysql.ts.template +16 -13
  80. package/dist/templates/dataSources/postgres.ts.template +15 -13
  81. package/dist/templates/dataset-generator-script.ts.template +139 -139
  82. package/dist/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
  83. package/dist/templates/datasets/mysql-default/Address.jsonl.template +3 -3
  84. package/dist/templates/datasets/mysql-default/App.jsonl.template +4 -4
  85. package/dist/templates/datasets/mysql-default/Company.jsonl.template +3 -3
  86. package/dist/templates/datasets/mysql-default/Person.jsonl.template +2 -2
  87. package/dist/templates/datasets/mysql-default/User.jsonl.template +1 -0
  88. package/dist/templates/datasets/mysql-default/instructions.md.template +1 -0
  89. package/dist/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
  90. package/dist/templates/datasets/postgres-default/Address.jsonl.template +3 -3
  91. package/dist/templates/datasets/postgres-default/App.jsonl.template +4 -4
  92. package/dist/templates/datasets/postgres-default/Company.jsonl.template +3 -3
  93. package/dist/templates/datasets/postgres-default/Person.jsonl.template +2 -2
  94. package/dist/templates/datasets/postgres-default/User.jsonl.template +1 -0
  95. package/dist/templates/datasets/postgres-default/instructions.md.template +1 -0
  96. package/dist/templates/docker-compose.prod-test.yml.template +32 -0
  97. package/dist/templates/docker-compose.yml.template +24 -0
  98. package/dist/templates/docs/app-description.md.template +33 -33
  99. package/dist/templates/firebase.json.template +68 -0
  100. package/dist/templates/frontend/.umirc.ts.template +23 -0
  101. package/dist/templates/frontend/package.json.template +45 -0
  102. package/dist/templates/frontend/public/config.json +6 -0
  103. package/dist/templates/frontend/public/logo.svg +6 -0
  104. package/dist/templates/frontend/src/app.tsx.template +44 -0
  105. package/dist/templates/frontend/src/global.less.template +117 -0
  106. package/dist/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
  107. package/dist/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
  108. package/dist/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
  109. package/dist/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
  110. package/dist/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
  111. package/dist/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
  112. package/dist/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
  113. package/dist/templates/frontend/tsconfig.json.template +50 -0
  114. package/dist/templates/gql/codegen.yml.template +25 -25
  115. package/dist/templates/gql/index.ts.template +17 -24
  116. package/dist/templates/gql/operations.graphql.template +30 -30
  117. package/dist/templates/ops/README.md.template +1045 -0
  118. package/dist/templates/ops/cloudbuild.yaml.template +161 -0
  119. package/dist/templates/ops/scripts/_utils.js.template +217 -0
  120. package/dist/templates/ops/scripts/deploy.js.template +145 -0
  121. package/dist/templates/ops/scripts/setup-gcp.js.template +330 -0
  122. package/dist/templates/ops/scripts/setup-secrets.js.template +76 -0
  123. package/dist/templates/ops/scripts/test-prod-local.js.template +49 -0
  124. package/dist/templates/package.json.template +50 -38
  125. package/dist/templates/pnpm-workspace.yaml.template +3 -0
  126. package/dist/templates/prompt-analysis.md.template +110 -110
  127. package/dist/templates/prompt-script-generation.md.template +258 -258
  128. package/dist/templates/src/Address.ts.template +28 -31
  129. package/dist/templates/src/App.ts.template +17 -61
  130. package/dist/templates/src/Company.ts.template +41 -47
  131. package/dist/templates/src/Models.test.ts.template +654 -654
  132. package/dist/templates/src/Person.test.ts.template +289 -289
  133. package/dist/templates/src/Person.ts.template +90 -105
  134. package/dist/templates/src/actions/index.ts.template +11 -11
  135. package/dist/templates/src/auth/permissions.ts.template +34 -0
  136. package/dist/templates/src/data/App.ts.template +48 -0
  137. package/dist/templates/src/data/User.ts.template +35 -0
  138. package/dist/templates/src/types/gql.d.ts.template +17 -17
  139. package/dist/templates/vscode/extensions.json +4 -3
  140. package/dist/templates/vscode/settings.json +17 -11
  141. package/dist/templates/workspace-package.json.template +21 -0
  142. package/dist/utils/buildCache.d.ts +12 -0
  143. package/dist/utils/buildCache.d.ts.map +1 -0
  144. package/dist/utils/buildCache.js +102 -0
  145. package/dist/utils/buildCache.js.map +1 -0
  146. package/dist/utils/checkFramework.d.ts +27 -0
  147. package/dist/utils/checkFramework.d.ts.map +1 -0
  148. package/dist/utils/checkFramework.js +104 -0
  149. package/dist/utils/checkFramework.js.map +1 -0
  150. package/dist/utils/datasourceParser.d.ts +11 -0
  151. package/dist/utils/datasourceParser.d.ts.map +1 -1
  152. package/dist/utils/datasourceParser.js +154 -56
  153. package/dist/utils/datasourceParser.js.map +1 -1
  154. package/dist/utils/dockerManager.d.ts +25 -0
  155. package/dist/utils/dockerManager.d.ts.map +1 -0
  156. package/dist/utils/dockerManager.js +281 -0
  157. package/dist/utils/dockerManager.js.map +1 -0
  158. package/dist/utils/infraFileParser.d.ts +26 -0
  159. package/dist/utils/infraFileParser.d.ts.map +1 -0
  160. package/dist/utils/infraFileParser.js +75 -0
  161. package/dist/utils/infraFileParser.js.map +1 -0
  162. package/dist/utils/jsonlLoader.d.ts +91 -12
  163. package/dist/utils/jsonlLoader.d.ts.map +1 -1
  164. package/dist/utils/jsonlLoader.js +674 -63
  165. package/dist/utils/jsonlLoader.js.map +1 -1
  166. package/dist/utils/model-analyzer.d.ts.map +1 -1
  167. package/dist/utils/model-analyzer.js +67 -13
  168. package/dist/utils/model-analyzer.js.map +1 -1
  169. package/dist/utils/userManagement.d.ts +57 -0
  170. package/dist/utils/userManagement.d.ts.map +1 -0
  171. package/dist/utils/userManagement.js +288 -0
  172. package/dist/utils/userManagement.js.map +1 -0
  173. package/dist/utils/viewsGenerator.d.ts +15 -0
  174. package/dist/utils/viewsGenerator.d.ts.map +1 -0
  175. package/dist/utils/viewsGenerator.js +311 -0
  176. package/dist/utils/viewsGenerator.js.map +1 -0
  177. package/oclif.manifest.json +445 -20
  178. package/package.json +29 -26
  179. package/src/templates/.env.template +23 -0
  180. package/src/templates/.firebaserc.template +5 -0
  181. package/src/templates/.github/copilot-instructions.md.template +652 -17
  182. package/src/templates/backend/Dockerfile.template +30 -0
  183. package/src/templates/config/datasource.ts.template +12 -9
  184. package/src/templates/config/jest.config.ts +30 -30
  185. package/src/templates/config/jest.setup.ts +1 -1
  186. package/src/templates/config/tsconfig.json.template +50 -29
  187. package/src/templates/dataSources/mysql.ts.template +16 -13
  188. package/src/templates/dataSources/postgres.ts.template +15 -13
  189. package/src/templates/dataset-generator-script.ts.template +139 -139
  190. package/src/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
  191. package/src/templates/datasets/mysql-default/Address.jsonl.template +3 -3
  192. package/src/templates/datasets/mysql-default/App.jsonl.template +4 -4
  193. package/src/templates/datasets/mysql-default/Company.jsonl.template +3 -3
  194. package/src/templates/datasets/mysql-default/Person.jsonl.template +2 -2
  195. package/src/templates/datasets/mysql-default/User.jsonl.template +1 -0
  196. package/src/templates/datasets/mysql-default/instructions.md.template +1 -0
  197. package/src/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
  198. package/src/templates/datasets/postgres-default/Address.jsonl.template +3 -3
  199. package/src/templates/datasets/postgres-default/App.jsonl.template +4 -4
  200. package/src/templates/datasets/postgres-default/Company.jsonl.template +3 -3
  201. package/src/templates/datasets/postgres-default/Person.jsonl.template +2 -2
  202. package/src/templates/datasets/postgres-default/User.jsonl.template +1 -0
  203. package/src/templates/datasets/postgres-default/instructions.md.template +1 -0
  204. package/src/templates/docker-compose.prod-test.yml.template +32 -0
  205. package/src/templates/docker-compose.yml.template +24 -0
  206. package/src/templates/docs/app-description.md.template +33 -33
  207. package/src/templates/firebase.json.template +68 -0
  208. package/src/templates/frontend/.umirc.ts.template +23 -0
  209. package/src/templates/frontend/package.json.template +45 -0
  210. package/src/templates/frontend/public/config.json +6 -0
  211. package/src/templates/frontend/public/logo.svg +6 -0
  212. package/src/templates/frontend/src/app.tsx.template +44 -0
  213. package/src/templates/frontend/src/global.less.template +117 -0
  214. package/src/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
  215. package/src/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
  216. package/src/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
  217. package/src/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
  218. package/src/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
  219. package/src/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
  220. package/src/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
  221. package/src/templates/frontend/tsconfig.json.template +50 -0
  222. package/src/templates/gql/codegen.yml.template +25 -25
  223. package/src/templates/gql/index.ts.template +17 -24
  224. package/src/templates/gql/operations.graphql.template +30 -30
  225. package/src/templates/ops/README.md.template +1045 -0
  226. package/src/templates/ops/cloudbuild.yaml.template +161 -0
  227. package/src/templates/ops/scripts/_utils.js.template +217 -0
  228. package/src/templates/ops/scripts/deploy.js.template +145 -0
  229. package/src/templates/ops/scripts/setup-gcp.js.template +330 -0
  230. package/src/templates/ops/scripts/setup-secrets.js.template +76 -0
  231. package/src/templates/ops/scripts/test-prod-local.js.template +49 -0
  232. package/src/templates/package.json.template +50 -38
  233. package/src/templates/pnpm-workspace.yaml.template +3 -0
  234. package/src/templates/prompt-analysis.md.template +110 -110
  235. package/src/templates/prompt-script-generation.md.template +258 -258
  236. package/src/templates/src/Address.ts.template +28 -31
  237. package/src/templates/src/App.ts.template +17 -61
  238. package/src/templates/src/Company.ts.template +41 -47
  239. package/src/templates/src/Models.test.ts.template +654 -654
  240. package/src/templates/src/Person.test.ts.template +289 -289
  241. package/src/templates/src/Person.ts.template +90 -105
  242. package/src/templates/src/actions/index.ts.template +11 -11
  243. package/src/templates/src/auth/permissions.ts.template +34 -0
  244. package/src/templates/src/data/App.ts.template +48 -0
  245. package/src/templates/src/data/User.ts.template +35 -0
  246. package/src/templates/src/types/gql.d.ts.template +17 -17
  247. package/src/templates/vscode/extensions.json +4 -3
  248. package/src/templates/vscode/settings.json +17 -11
  249. package/src/templates/workspace-package.json.template +21 -0
  250. package/dist/templates/src/index.ts +0 -66
  251. package/src/templates/src/index.ts +0 -66
@@ -1,655 +1,655 @@
1
- import { App } from './App';
2
- import { Person } from './Person';
3
- import { Company } from './Company';
4
- import { Address } from './Address';
5
-
6
- describe('Models Integration Tests', () => {
7
- describe('Person Model', () => {
8
- it('should validate a valid adult person', async () => {
9
- const personData = {
10
- firstName: 'John',
11
- lastName: 'Doe',
12
- email: 'john@example.com',
13
- age: 25,
14
- phoneNumber: '123-456-7890',
15
- additionalInfo: '<p>Senior Developer</p>',
16
- isActive: true
17
- };
18
-
19
- const person = Person.fromJSON(personData);
20
- const errors = await person.validate();
21
-
22
- expect(errors.length).toBe(0);
23
- expect(person.firstName).toBe('John');
24
- expect(person.lastName).toBe('Doe');
25
- expect(person.age).toBe(25);
26
- expect(person.isActive).toBe(true);
27
- });
28
-
29
- it('should validate a valid minor person with parent email', async () => {
30
- const personData = {
31
- firstName: 'Jane',
32
- lastName: 'Smith',
33
- email: 'jane@example.com',
34
- age: 16,
35
- parentEmail: 'parent@example.com',
36
- additionalInfo: '<p>Student</p>',
37
- isActive: true
38
- };
39
-
40
- const person = Person.fromJSON(personData);
41
- const errors = await person.validate();
42
-
43
- expect(errors.length).toBe(0);
44
- expect(person.parentEmail).toBe('parent@example.com');
45
- });
46
-
47
- it('should fail validation when firstName is too short', async () => {
48
- const personData = {
49
- firstName: 'J',
50
- lastName: 'Doe',
51
- email: 'john@example.com',
52
- age: 25
53
- };
54
-
55
- const person = Person.fromJSON(personData);
56
- const errors = await person.validate();
57
-
58
- expect(errors.length).toBeGreaterThan(0);
59
- const firstNameError = errors.find(e => e.property === 'firstName');
60
- expect(firstNameError).toBeDefined();
61
- expect(firstNameError?.constraints).toHaveProperty('minLength');
62
- });
63
-
64
- it('should fail validation when firstName contains numbers', async () => {
65
- const personData = {
66
- firstName: 'John123',
67
- lastName: 'Doe',
68
- email: 'john@example.com',
69
- age: 25
70
- };
71
-
72
- const person = Person.fromJSON(personData);
73
- const errors = await person.validate();
74
-
75
- expect(errors.length).toBeGreaterThan(0);
76
- const firstNameError = errors.find(e => e.property === 'firstName');
77
- expect(firstNameError).toBeDefined();
78
- expect(firstNameError?.constraints).toHaveProperty('matches');
79
- });
80
-
81
- it('should fail validation when email is invalid', async () => {
82
- const personData = {
83
- firstName: 'John',
84
- lastName: 'Doe',
85
- email: 'invalid-email',
86
- age: 25
87
- };
88
-
89
- const person = Person.fromJSON(personData);
90
- const errors = await person.validate();
91
-
92
- expect(errors.length).toBeGreaterThan(0);
93
- const emailError = errors.find(e => e.property === 'email');
94
- expect(emailError).toBeDefined();
95
- expect(emailError?.constraints).toHaveProperty('isEmail');
96
- });
97
-
98
- it('should fail validation when age is negative', async () => {
99
- const personData = {
100
- firstName: 'John',
101
- lastName: 'Doe',
102
- email: 'john@example.com',
103
- age: -5
104
- };
105
-
106
- const person = Person.fromJSON(personData);
107
- const errors = await person.validate();
108
-
109
- expect(errors.length).toBeGreaterThan(0);
110
- const ageError = errors.find(e => e.property === 'age');
111
- expect(ageError).toBeDefined();
112
- expect(ageError?.constraints).toHaveProperty('invalidAge');
113
- });
114
-
115
- it('should require parentEmail for minors', async () => {
116
- const personData = {
117
- firstName: 'Jane',
118
- lastName: 'Smith',
119
- email: 'jane@example.com',
120
- age: 16
121
- // parentEmail missing
122
- };
123
-
124
- const person = Person.fromJSON(personData);
125
- const errors = await person.validate();
126
-
127
- expect(errors.length).toBeGreaterThan(0);
128
- const parentEmailError = errors.find(e => e.property === 'parentEmail');
129
- expect(parentEmailError).toBeDefined();
130
- });
131
-
132
- it('should exclude internalId from JSON output', () => {
133
- const personData = {
134
- firstName: 'John',
135
- lastName: 'Doe',
136
- email: 'john@example.com',
137
- age: 25,
138
- internalId: 'secret-123',
139
- isActive: true
140
- };
141
-
142
- const person = Person.fromJSON(personData);
143
- const json = person.toJSON();
144
-
145
- expect(json).not.toHaveProperty('internalId');
146
- expect(json).toHaveProperty('firstName', 'John');
147
- });
148
- });
149
-
150
- describe('Company Model', () => {
151
- let validCEO: Person;
152
-
153
- beforeEach(() => {
154
- const ceoData = {
155
- firstName: 'Robert',
156
- lastName: 'Johnson',
157
- email: 'robert@company.com',
158
- age: 45,
159
- phoneNumber: '+1987654321',
160
- additionalInfo: '<p>CEO and Founder</p>',
161
- isActive: true
162
- };
163
- validCEO = Person.fromJSON(ceoData);
164
- });
165
-
166
- it('should validate a valid company with CEO composition', async () => {
167
- const companyData = {
168
- name: "TechCorp Solutions",
169
- ceo: validCEO
170
- };
171
-
172
- const company = Company.fromJSON(companyData);
173
- const errors = await company.validate();
174
-
175
- expect(errors.length).toBe(0);
176
- expect(company.name).toBe("TechCorp Solutions");
177
- expect(company.ceo).toBeDefined();
178
- expect(company.ceo.firstName).toBe("Robert");
179
- expect(company.ceo.lastName).toBe("Johnson");
180
- });
181
-
182
- it('should fail validation with invalid company name (too short)', async () => {
183
- const companyData = {
184
- name: "X", // Too short
185
- ceo: validCEO
186
- };
187
-
188
- const company = Company.fromJSON(companyData);
189
- const errors = await company.validate();
190
-
191
- expect(errors.length).toBeGreaterThan(0);
192
- const nameError = errors.find(e => e.property === 'name');
193
- expect(nameError).toBeDefined();
194
- expect(nameError?.constraints).toHaveProperty('minLength');
195
- });
196
-
197
- it('should fail validation with invalid company name (special chars)', async () => {
198
- const companyData = {
199
- name: "Company@#$%", // Invalid characters
200
- ceo: validCEO
201
- };
202
-
203
- const company = Company.fromJSON(companyData);
204
- const errors = await company.validate();
205
-
206
- expect(errors.length).toBeGreaterThan(0);
207
- const nameError = errors.find(e => e.property === 'name');
208
- expect(nameError).toBeDefined();
209
- expect(nameError?.constraints).toHaveProperty('matches');
210
- });
211
-
212
- it('should fail validation when CEO is missing', async () => {
213
- const companyData = {
214
- name: "ValidCompany"
215
- // ceo is missing
216
- };
217
-
218
- const company = Company.fromJSON(companyData);
219
- const errors = await company.validate();
220
-
221
- expect(errors.length).toBeGreaterThan(0);
222
- const ceoError = errors.find(e => e.property === 'ceo');
223
- expect(ceoError).toBeDefined();
224
- });
225
-
226
- it('should validate company with valid name containing dots and ampersands', async () => {
227
- const companyData = {
228
- name: "Tech & Solutions Co.",
229
- ceo: validCEO
230
- };
231
-
232
- const company = Company.fromJSON(companyData);
233
- const errors = await company.validate();
234
-
235
- expect(errors.length).toBe(0);
236
- expect(company.name).toBe("Tech & Solutions Co.");
237
- });
238
- });
239
-
240
- describe('App Model', () => {
241
- let validCompany: Company;
242
-
243
- beforeEach(() => {
244
- const ceoData = {
245
- firstName: 'Sarah',
246
- lastName: 'Davis',
247
- email: 'sarah@company.com',
248
- age: 40,
249
- phoneNumber: '+1111222333',
250
- additionalInfo: '<p>Visionary CEO</p>',
251
- isActive: true
252
- };
253
- const ceo = Person.fromJSON(ceoData);
254
-
255
- const companyData = {
256
- name: "TechCorp Solutions",
257
- ceo: ceo
258
- };
259
- validCompany = Company.fromJSON(companyData);
260
- });
261
-
262
- it('should validate a valid app with owner company reference', async () => {
263
- const appData = {
264
- name: "SlingrApp",
265
- version: "01.00.01",
266
- description: "A comprehensive application framework for building modern web applications",
267
- ownerCompany: validCompany
268
- };
269
-
270
- const app = App.fromJSON(appData);
271
- const errors = await app.validate();
272
-
273
- expect(errors.length).toBe(0);
274
- expect(app.name).toBe("SlingrApp");
275
- expect(app.version).toBe("01.00.01");
276
- expect(app.ownerCompany).toBeDefined();
277
- expect(app.ownerCompany.name).toBe("TechCorp Solutions");
278
- });
279
-
280
- it('should fail validation with invalid name format (starts with dot)', async () => {
281
- const appData = {
282
- name: ".InvalidApp",
283
- version: "01.00.01",
284
- description: "A comprehensive application framework",
285
- ownerCompany: validCompany
286
- };
287
-
288
- const app = App.fromJSON(appData);
289
- const errors = await app.validate();
290
-
291
- expect(errors.length).toBeGreaterThan(0);
292
- const nameError = errors.find(e => e.property === 'name');
293
- expect(nameError).toBeDefined();
294
- expect(nameError?.constraints).toHaveProperty('matches');
295
- });
296
-
297
- it('should fail validation with invalid version format', async () => {
298
- const appData = {
299
- name: "ValidApp",
300
- version: "1.0.1", // Should be 01.00.01
301
- description: "A comprehensive application framework",
302
- ownerCompany: validCompany
303
- };
304
-
305
- const app = App.fromJSON(appData);
306
- const errors = await app.validate();
307
-
308
- expect(errors.length).toBeGreaterThan(0);
309
- const versionError = errors.find(e => e.property === 'version');
310
- expect(versionError).toBeDefined();
311
- expect(versionError?.constraints).toHaveProperty('matches');
312
- });
313
-
314
- it('should fail validation when owner company is missing', async () => {
315
- const appData = {
316
- name: "ValidApp",
317
- version: "01.00.01",
318
- description: "A comprehensive application framework"
319
- // ownerCompany is missing
320
- };
321
-
322
- const app = App.fromJSON(appData);
323
- const errors = await app.validate();
324
-
325
- expect(errors.length).toBeGreaterThan(0);
326
- const ownerError = errors.find(e => e.property === 'ownerCompany');
327
- expect(ownerError).toBeDefined();
328
- });
329
-
330
- it('should validate app with valid name containing dots', async () => {
331
- const appData = {
332
- name: "App.Module.Core",
333
- version: "02.05.12",
334
- description: "A modular application core component",
335
- ownerCompany: validCompany
336
- };
337
-
338
- const app = App.fromJSON(appData);
339
- const errors = await app.validate();
340
-
341
- expect(errors.length).toBe(0);
342
- expect(app.name).toBe("App.Module.Core");
343
- });
344
- });
345
-
346
- describe('Address Model', () => {
347
- it('should validate a valid address', async () => {
348
- const addressData = {
349
- street: '123 Main Street',
350
- zipCode: '12345',
351
- country: 'United States'
352
- };
353
-
354
- const address = Address.fromJSON(addressData);
355
- const errors = await address.validate();
356
-
357
- expect(errors.length).toBe(0);
358
- expect(address.street).toBe('123 Main Street');
359
- expect(address.zipCode).toBe('12345');
360
- expect(address.country).toBe('United States');
361
- });
362
-
363
- it('should fail validation when street is missing', async () => {
364
- const addressData = {
365
- zipCode: '12345',
366
- country: 'United States'
367
- // street is missing
368
- };
369
-
370
- const address = Address.fromJSON(addressData);
371
- const errors = await address.validate();
372
-
373
- expect(errors.length).toBeGreaterThan(0);
374
- const streetError = errors.find(e => e.property === 'street');
375
- expect(streetError).toBeDefined();
376
- });
377
-
378
- });
379
-
380
- describe('Person with Address (SharedComposition)', () => {
381
- let validAddress: Address;
382
-
383
- beforeEach(() => {
384
- const addressData = {
385
- street: '456 Elm Avenue',
386
- zipCode: '67890',
387
- country: 'Canada'
388
- };
389
- validAddress = Address.fromJSON(addressData);
390
- });
391
-
392
- it('should validate a person with shared address composition', async () => {
393
- const personData = {
394
- firstName: 'John',
395
- lastName: 'Doe',
396
- email: 'john@example.com',
397
- age: 25,
398
- phoneNumber: '123-456-7890',
399
- isActive: true,
400
- address: validAddress
401
- };
402
-
403
- const person = Person.fromJSON(personData);
404
- const errors = await person.validate();
405
-
406
- expect(errors.length).toBe(0);
407
- expect(person.address).toBeDefined();
408
- expect(person.address.street).toBe('456 Elm Avenue');
409
- expect(person.address.zipCode).toBe('67890');
410
- expect(person.address.country).toBe('Canada');
411
- });
412
-
413
- it('should validate a person without address (optional shared composition)', async () => {
414
- const personData = {
415
- firstName: 'Jane',
416
- lastName: 'Smith',
417
- email: 'jane@example.com',
418
- age: 30,
419
- phoneNumber: '987-654-3210',
420
- isActive: true
421
- // address is optional
422
- };
423
-
424
- const person = Person.fromJSON(personData);
425
- const errors = await person.validate();
426
-
427
- expect(errors.length).toBe(0);
428
- expect(person.address).toBeUndefined();
429
- });
430
-
431
- it('should fail validation when person has invalid address', async () => {
432
- const invalidAddressData = {
433
- street: '789 Oak Street',
434
- zipCode: '11111'
435
- // country is missing, making address invalid
436
- };
437
- const invalidAddress = Address.fromJSON(invalidAddressData);
438
-
439
- const personData = {
440
- firstName: 'Bob',
441
- lastName: 'Wilson',
442
- email: 'bob@example.com',
443
- age: 35,
444
- phoneNumber: '555-123-4567',
445
- isActive: true,
446
- address: invalidAddress
447
- };
448
-
449
- const person = Person.fromJSON(personData);
450
- const errors = await person.validate();
451
-
452
- expect(errors.length).toBeGreaterThan(0);
453
- // Should contain nested validation errors from address
454
- const addressErrors = errors.filter(e => e.property?.includes('address'));
455
- expect(addressErrors.length).toBeGreaterThan(0);
456
- });
457
-
458
- it('should properly serialize person with address to JSON', () => {
459
- const personData = {
460
- firstName: 'Alice',
461
- lastName: 'Johnson',
462
- email: 'alice@example.com',
463
- age: 28,
464
- phoneNumber: '444-555-6666',
465
- isActive: true,
466
- address: validAddress
467
- };
468
-
469
- const person = Person.fromJSON(personData);
470
- const json = person.toJSON();
471
-
472
- expect(json).toHaveProperty('address');
473
- expect(json.address).toHaveProperty('street', '456 Elm Avenue');
474
- expect(json.address).toHaveProperty('zipCode', '67890');
475
- expect(json.address).toHaveProperty('country', 'Canada');
476
- });
477
- });
478
-
479
- describe('Company with Address (SharedComposition)', () => {
480
- let validCEO: Person;
481
- let validAddress: Address;
482
-
483
- beforeEach(() => {
484
- const ceoData = {
485
- firstName: 'Robert',
486
- lastName: 'Johnson',
487
- email: 'robert@company.com',
488
- age: 45,
489
- phoneNumber: '+1987654321',
490
- additionalInfo: '<p>CEO and Founder</p>',
491
- isActive: true
492
- };
493
- validCEO = Person.fromJSON(ceoData);
494
-
495
- const addressData = {
496
- street: '100 Corporate Plaza',
497
- zipCode: '98765',
498
- country: 'United States'
499
- };
500
- validAddress = Address.fromJSON(addressData);
501
- });
502
-
503
- it('should validate a company with shared address composition', async () => {
504
- const companyData = {
505
- name: "TechCorp Solutions",
506
- ceo: validCEO,
507
- address: validAddress
508
- };
509
-
510
- const company = Company.fromJSON(companyData);
511
- const errors = await company.validate();
512
-
513
- expect(errors.length).toBe(0);
514
- expect(company.address).toBeDefined();
515
- expect(company.address.street).toBe('100 Corporate Plaza');
516
- expect(company.address.zipCode).toBe('98765');
517
- expect(company.address.country).toBe('United States');
518
- });
519
-
520
- it('should validate a company without address (optional shared composition)', async () => {
521
- const companyData = {
522
- name: "StartupCorp",
523
- ceo: validCEO
524
- // address is optional
525
- };
526
-
527
- const company = Company.fromJSON(companyData);
528
- const errors = await company.validate();
529
-
530
- expect(errors.length).toBe(0);
531
- expect(company.address).toBeUndefined();
532
- });
533
-
534
- it('should fail validation when company has invalid address', async () => {
535
- const invalidAddressData = {
536
- street: '200 Business Street'
537
- // zipCode and country are missing, making address invalid
538
- };
539
- const invalidAddress = Address.fromJSON(invalidAddressData);
540
-
541
- const companyData = {
542
- name: "FailCorp",
543
- ceo: validCEO,
544
- address: invalidAddress
545
- };
546
-
547
- const company = Company.fromJSON(companyData);
548
- const errors = await company.validate();
549
-
550
- expect(errors.length).toBeGreaterThan(0);
551
- // Should contain nested validation errors from address
552
- const addressErrors = errors.filter(e => e.property?.includes('address'));
553
- expect(addressErrors.length).toBeGreaterThan(0);
554
- });
555
-
556
- it('should properly serialize company with address to JSON', () => {
557
- const companyData = {
558
- name: "GlobalTech Inc",
559
- ceo: validCEO,
560
- address: validAddress
561
- };
562
-
563
- const company = Company.fromJSON(companyData);
564
- const json = company.toJSON();
565
-
566
- expect(json).toHaveProperty('address');
567
- expect(json.address).toHaveProperty('street', '100 Corporate Plaza');
568
- expect(json.address).toHaveProperty('zipCode', '98765');
569
- expect(json.address).toHaveProperty('country', 'United States');
570
- });
571
- });
572
-
573
- describe('Full Integration Tests', () => {
574
- it('should handle complete model relationships', async () => {
575
- // Create a CEO
576
- const ceoData = {
577
- firstName: 'John',
578
- lastName: 'Smith',
579
- email: 'john.smith@techcorp.com',
580
- age: 45,
581
- phoneNumber: '+1555123456',
582
- additionalInfo: '<p>Experienced technology leader</p>',
583
- isActive: true
584
- };
585
- const ceo = Person.fromJSON(ceoData);
586
-
587
- // Create a company with the CEO (composition)
588
- const companyData = {
589
- name: "TechCorp Innovation Labs",
590
- ceo: ceo
591
- };
592
- const company = Company.fromJSON(companyData);
593
-
594
- // Create an app that references the company
595
- const appData = {
596
- name: "InnovationPlatform",
597
- version: "03.01.05",
598
- description: "A cutting-edge platform for technological innovation and development",
599
- ownerCompany: company
600
- };
601
- const app = App.fromJSON(appData);
602
-
603
- // Validate all models
604
- const ceoErrors = await ceo.validate();
605
- const companyErrors = await company.validate();
606
- const appErrors = await app.validate();
607
-
608
- expect(ceoErrors.length).toBe(0);
609
- expect(companyErrors.length).toBe(0);
610
- expect(appErrors.length).toBe(0);
611
-
612
- // Verify relationships
613
- expect(app.ownerCompany.name).toBe("TechCorp Innovation Labs");
614
- expect(app.ownerCompany.ceo.firstName).toBe("John");
615
- expect(company.ceo.email).toBe("john.smith@techcorp.com");
616
- });
617
-
618
- it('should properly serialize complete model hierarchy', () => {
619
- const ceoData = {
620
- firstName: 'Alice',
621
- lastName: 'Wilson',
622
- email: 'alice@startup.com',
623
- age: 35,
624
- phoneNumber: '+1444555666',
625
- isActive: true
626
- };
627
- const ceo = Person.fromJSON(ceoData);
628
-
629
- const companyData = {
630
- name: "StartupCorp",
631
- ceo: ceo
632
- };
633
- const company = Company.fromJSON(companyData);
634
-
635
- const appData = {
636
- name: "StartupApp",
637
- version: "01.00.01",
638
- description: "A revolutionary startup application",
639
- ownerCompany: company
640
- };
641
- const app = App.fromJSON(appData);
642
-
643
- // Serialize to JSON
644
- const appJson = app.toJSON();
645
-
646
- // Verify the JSON structure
647
- expect(appJson).toHaveProperty('name', 'StartupApp');
648
- expect(appJson).toHaveProperty('version', '01.00.01');
649
- expect(appJson).toHaveProperty('ownerCompany');
650
- expect(appJson.ownerCompany).toHaveProperty('name', 'StartupCorp');
651
- expect(appJson.ownerCompany).toHaveProperty('ceo');
652
- expect(appJson.ownerCompany.ceo).toHaveProperty('firstName', 'Alice');
653
- });
654
- });
1
+ import { App } from './App';
2
+ import { Person } from './Person';
3
+ import { Company } from './Company';
4
+ import { Address } from './Address';
5
+
6
+ describe('Models Integration Tests', () => {
7
+ describe('Person Model', () => {
8
+ it('should validate a valid adult person', async () => {
9
+ const personData = {
10
+ firstName: 'John',
11
+ lastName: 'Doe',
12
+ email: 'john@example.com',
13
+ age: 25,
14
+ phoneNumber: '123-456-7890',
15
+ additionalInfo: '<p>Senior Developer</p>',
16
+ isActive: true
17
+ };
18
+
19
+ const person = Person.fromJSON(personData);
20
+ const errors = await person.validate();
21
+
22
+ expect(errors.length).toBe(0);
23
+ expect(person.firstName).toBe('John');
24
+ expect(person.lastName).toBe('Doe');
25
+ expect(person.age).toBe(25);
26
+ expect(person.isActive).toBe(true);
27
+ });
28
+
29
+ it('should validate a valid minor person with parent email', async () => {
30
+ const personData = {
31
+ firstName: 'Jane',
32
+ lastName: 'Smith',
33
+ email: 'jane@example.com',
34
+ age: 16,
35
+ parentEmail: 'parent@example.com',
36
+ additionalInfo: '<p>Student</p>',
37
+ isActive: true
38
+ };
39
+
40
+ const person = Person.fromJSON(personData);
41
+ const errors = await person.validate();
42
+
43
+ expect(errors.length).toBe(0);
44
+ expect(person.parentEmail).toBe('parent@example.com');
45
+ });
46
+
47
+ it('should fail validation when firstName is too short', async () => {
48
+ const personData = {
49
+ firstName: 'J',
50
+ lastName: 'Doe',
51
+ email: 'john@example.com',
52
+ age: 25
53
+ };
54
+
55
+ const person = Person.fromJSON(personData);
56
+ const errors = await person.validate();
57
+
58
+ expect(errors.length).toBeGreaterThan(0);
59
+ const firstNameError = errors.find(e => e.property === 'firstName');
60
+ expect(firstNameError).toBeDefined();
61
+ expect(firstNameError?.constraints).toHaveProperty('minLength');
62
+ });
63
+
64
+ it('should fail validation when firstName contains numbers', async () => {
65
+ const personData = {
66
+ firstName: 'John123',
67
+ lastName: 'Doe',
68
+ email: 'john@example.com',
69
+ age: 25
70
+ };
71
+
72
+ const person = Person.fromJSON(personData);
73
+ const errors = await person.validate();
74
+
75
+ expect(errors.length).toBeGreaterThan(0);
76
+ const firstNameError = errors.find(e => e.property === 'firstName');
77
+ expect(firstNameError).toBeDefined();
78
+ expect(firstNameError?.constraints).toHaveProperty('matches');
79
+ });
80
+
81
+ it('should fail validation when email is invalid', async () => {
82
+ const personData = {
83
+ firstName: 'John',
84
+ lastName: 'Doe',
85
+ email: 'invalid-email',
86
+ age: 25
87
+ };
88
+
89
+ const person = Person.fromJSON(personData);
90
+ const errors = await person.validate();
91
+
92
+ expect(errors.length).toBeGreaterThan(0);
93
+ const emailError = errors.find(e => e.property === 'email');
94
+ expect(emailError).toBeDefined();
95
+ expect(emailError?.constraints).toHaveProperty('isEmail');
96
+ });
97
+
98
+ it('should fail validation when age is negative', async () => {
99
+ const personData = {
100
+ firstName: 'John',
101
+ lastName: 'Doe',
102
+ email: 'john@example.com',
103
+ age: -5
104
+ };
105
+
106
+ const person = Person.fromJSON(personData);
107
+ const errors = await person.validate();
108
+
109
+ expect(errors.length).toBeGreaterThan(0);
110
+ const ageError = errors.find(e => e.property === 'age');
111
+ expect(ageError).toBeDefined();
112
+ expect(ageError?.constraints).toHaveProperty('invalidAge');
113
+ });
114
+
115
+ it('should require parentEmail for minors', async () => {
116
+ const personData = {
117
+ firstName: 'Jane',
118
+ lastName: 'Smith',
119
+ email: 'jane@example.com',
120
+ age: 16
121
+ // parentEmail missing
122
+ };
123
+
124
+ const person = Person.fromJSON(personData);
125
+ const errors = await person.validate();
126
+
127
+ expect(errors.length).toBeGreaterThan(0);
128
+ const parentEmailError = errors.find(e => e.property === 'parentEmail');
129
+ expect(parentEmailError).toBeDefined();
130
+ });
131
+
132
+ it('should exclude internalId from JSON output', () => {
133
+ const personData = {
134
+ firstName: 'John',
135
+ lastName: 'Doe',
136
+ email: 'john@example.com',
137
+ age: 25,
138
+ internalId: 'secret-123',
139
+ isActive: true
140
+ };
141
+
142
+ const person = Person.fromJSON(personData);
143
+ const json = person.toJSON();
144
+
145
+ expect(json).not.toHaveProperty('internalId');
146
+ expect(json).toHaveProperty('firstName', 'John');
147
+ });
148
+ });
149
+
150
+ describe('Company Model', () => {
151
+ let validCEO: Person;
152
+
153
+ beforeEach(() => {
154
+ const ceoData = {
155
+ firstName: 'Robert',
156
+ lastName: 'Johnson',
157
+ email: 'robert@company.com',
158
+ age: 45,
159
+ phoneNumber: '+1987654321',
160
+ additionalInfo: '<p>CEO and Founder</p>',
161
+ isActive: true
162
+ };
163
+ validCEO = Person.fromJSON(ceoData);
164
+ });
165
+
166
+ it('should validate a valid company with CEO composition', async () => {
167
+ const companyData = {
168
+ name: "TechCorp Solutions",
169
+ ceo: validCEO
170
+ };
171
+
172
+ const company = Company.fromJSON(companyData);
173
+ const errors = await company.validate();
174
+
175
+ expect(errors.length).toBe(0);
176
+ expect(company.name).toBe("TechCorp Solutions");
177
+ expect(company.ceo).toBeDefined();
178
+ expect(company.ceo.firstName).toBe("Robert");
179
+ expect(company.ceo.lastName).toBe("Johnson");
180
+ });
181
+
182
+ it('should fail validation with invalid company name (too short)', async () => {
183
+ const companyData = {
184
+ name: "X", // Too short
185
+ ceo: validCEO
186
+ };
187
+
188
+ const company = Company.fromJSON(companyData);
189
+ const errors = await company.validate();
190
+
191
+ expect(errors.length).toBeGreaterThan(0);
192
+ const nameError = errors.find(e => e.property === 'name');
193
+ expect(nameError).toBeDefined();
194
+ expect(nameError?.constraints).toHaveProperty('minLength');
195
+ });
196
+
197
+ it('should fail validation with invalid company name (special chars)', async () => {
198
+ const companyData = {
199
+ name: "Company@#$%", // Invalid characters
200
+ ceo: validCEO
201
+ };
202
+
203
+ const company = Company.fromJSON(companyData);
204
+ const errors = await company.validate();
205
+
206
+ expect(errors.length).toBeGreaterThan(0);
207
+ const nameError = errors.find(e => e.property === 'name');
208
+ expect(nameError).toBeDefined();
209
+ expect(nameError?.constraints).toHaveProperty('matches');
210
+ });
211
+
212
+ it('should fail validation when CEO is missing', async () => {
213
+ const companyData = {
214
+ name: "ValidCompany"
215
+ // ceo is missing
216
+ };
217
+
218
+ const company = Company.fromJSON(companyData);
219
+ const errors = await company.validate();
220
+
221
+ expect(errors.length).toBeGreaterThan(0);
222
+ const ceoError = errors.find(e => e.property === 'ceo');
223
+ expect(ceoError).toBeDefined();
224
+ });
225
+
226
+ it('should validate company with valid name containing dots and ampersands', async () => {
227
+ const companyData = {
228
+ name: "Tech & Solutions Co.",
229
+ ceo: validCEO
230
+ };
231
+
232
+ const company = Company.fromJSON(companyData);
233
+ const errors = await company.validate();
234
+
235
+ expect(errors.length).toBe(0);
236
+ expect(company.name).toBe("Tech & Solutions Co.");
237
+ });
238
+ });
239
+
240
+ describe('App Model', () => {
241
+ let validCompany: Company;
242
+
243
+ beforeEach(() => {
244
+ const ceoData = {
245
+ firstName: 'Sarah',
246
+ lastName: 'Davis',
247
+ email: 'sarah@company.com',
248
+ age: 40,
249
+ phoneNumber: '+1111222333',
250
+ additionalInfo: '<p>Visionary CEO</p>',
251
+ isActive: true
252
+ };
253
+ const ceo = Person.fromJSON(ceoData);
254
+
255
+ const companyData = {
256
+ name: "TechCorp Solutions",
257
+ ceo: ceo
258
+ };
259
+ validCompany = Company.fromJSON(companyData);
260
+ });
261
+
262
+ it('should validate a valid app with owner company reference', async () => {
263
+ const appData = {
264
+ name: "SlingrApp",
265
+ version: "01.00.01",
266
+ description: "A comprehensive application framework for building modern web applications",
267
+ ownerCompany: validCompany
268
+ };
269
+
270
+ const app = App.fromJSON(appData);
271
+ const errors = await app.validate();
272
+
273
+ expect(errors.length).toBe(0);
274
+ expect(app.name).toBe("SlingrApp");
275
+ expect(app.version).toBe("01.00.01");
276
+ expect(app.ownerCompany).toBeDefined();
277
+ expect(app.ownerCompany.name).toBe("TechCorp Solutions");
278
+ });
279
+
280
+ it('should fail validation with invalid name format (starts with dot)', async () => {
281
+ const appData = {
282
+ name: ".InvalidApp",
283
+ version: "01.00.01",
284
+ description: "A comprehensive application framework",
285
+ ownerCompany: validCompany
286
+ };
287
+
288
+ const app = App.fromJSON(appData);
289
+ const errors = await app.validate();
290
+
291
+ expect(errors.length).toBeGreaterThan(0);
292
+ const nameError = errors.find(e => e.property === 'name');
293
+ expect(nameError).toBeDefined();
294
+ expect(nameError?.constraints).toHaveProperty('matches');
295
+ });
296
+
297
+ it('should fail validation with invalid version format', async () => {
298
+ const appData = {
299
+ name: "ValidApp",
300
+ version: "1.0.1", // Should be 01.00.01
301
+ description: "A comprehensive application framework",
302
+ ownerCompany: validCompany
303
+ };
304
+
305
+ const app = App.fromJSON(appData);
306
+ const errors = await app.validate();
307
+
308
+ expect(errors.length).toBeGreaterThan(0);
309
+ const versionError = errors.find(e => e.property === 'version');
310
+ expect(versionError).toBeDefined();
311
+ expect(versionError?.constraints).toHaveProperty('matches');
312
+ });
313
+
314
+ it('should fail validation when owner company is missing', async () => {
315
+ const appData = {
316
+ name: "ValidApp",
317
+ version: "01.00.01",
318
+ description: "A comprehensive application framework"
319
+ // ownerCompany is missing
320
+ };
321
+
322
+ const app = App.fromJSON(appData);
323
+ const errors = await app.validate();
324
+
325
+ expect(errors.length).toBeGreaterThan(0);
326
+ const ownerError = errors.find(e => e.property === 'ownerCompany');
327
+ expect(ownerError).toBeDefined();
328
+ });
329
+
330
+ it('should validate app with valid name containing dots', async () => {
331
+ const appData = {
332
+ name: "App.Module.Core",
333
+ version: "02.05.12",
334
+ description: "A modular application core component",
335
+ ownerCompany: validCompany
336
+ };
337
+
338
+ const app = App.fromJSON(appData);
339
+ const errors = await app.validate();
340
+
341
+ expect(errors.length).toBe(0);
342
+ expect(app.name).toBe("App.Module.Core");
343
+ });
344
+ });
345
+
346
+ describe('Address Model', () => {
347
+ it('should validate a valid address', async () => {
348
+ const addressData = {
349
+ street: '123 Main Street',
350
+ zipCode: '12345',
351
+ country: 'United States'
352
+ };
353
+
354
+ const address = Address.fromJSON(addressData);
355
+ const errors = await address.validate();
356
+
357
+ expect(errors.length).toBe(0);
358
+ expect(address.street).toBe('123 Main Street');
359
+ expect(address.zipCode).toBe('12345');
360
+ expect(address.country).toBe('United States');
361
+ });
362
+
363
+ it('should fail validation when street is missing', async () => {
364
+ const addressData = {
365
+ zipCode: '12345',
366
+ country: 'United States'
367
+ // street is missing
368
+ };
369
+
370
+ const address = Address.fromJSON(addressData);
371
+ const errors = await address.validate();
372
+
373
+ expect(errors.length).toBeGreaterThan(0);
374
+ const streetError = errors.find(e => e.property === 'street');
375
+ expect(streetError).toBeDefined();
376
+ });
377
+
378
+ });
379
+
380
+ describe('Person with Address (SharedComposition)', () => {
381
+ let validAddress: Address;
382
+
383
+ beforeEach(() => {
384
+ const addressData = {
385
+ street: '456 Elm Avenue',
386
+ zipCode: '67890',
387
+ country: 'Canada'
388
+ };
389
+ validAddress = Address.fromJSON(addressData);
390
+ });
391
+
392
+ it('should validate a person with shared address composition', async () => {
393
+ const personData = {
394
+ firstName: 'John',
395
+ lastName: 'Doe',
396
+ email: 'john@example.com',
397
+ age: 25,
398
+ phoneNumber: '123-456-7890',
399
+ isActive: true,
400
+ address: validAddress
401
+ };
402
+
403
+ const person = Person.fromJSON(personData);
404
+ const errors = await person.validate();
405
+
406
+ expect(errors.length).toBe(0);
407
+ expect(person.address).toBeDefined();
408
+ expect(person.address.street).toBe('456 Elm Avenue');
409
+ expect(person.address.zipCode).toBe('67890');
410
+ expect(person.address.country).toBe('Canada');
411
+ });
412
+
413
+ it('should validate a person without address (optional shared composition)', async () => {
414
+ const personData = {
415
+ firstName: 'Jane',
416
+ lastName: 'Smith',
417
+ email: 'jane@example.com',
418
+ age: 30,
419
+ phoneNumber: '987-654-3210',
420
+ isActive: true
421
+ // address is optional
422
+ };
423
+
424
+ const person = Person.fromJSON(personData);
425
+ const errors = await person.validate();
426
+
427
+ expect(errors.length).toBe(0);
428
+ expect(person.address).toBeUndefined();
429
+ });
430
+
431
+ it('should fail validation when person has invalid address', async () => {
432
+ const invalidAddressData = {
433
+ street: '789 Oak Street',
434
+ zipCode: '11111'
435
+ // country is missing, making address invalid
436
+ };
437
+ const invalidAddress = Address.fromJSON(invalidAddressData);
438
+
439
+ const personData = {
440
+ firstName: 'Bob',
441
+ lastName: 'Wilson',
442
+ email: 'bob@example.com',
443
+ age: 35,
444
+ phoneNumber: '555-123-4567',
445
+ isActive: true,
446
+ address: invalidAddress
447
+ };
448
+
449
+ const person = Person.fromJSON(personData);
450
+ const errors = await person.validate();
451
+
452
+ expect(errors.length).toBeGreaterThan(0);
453
+ // Should contain nested validation errors from address
454
+ const addressErrors = errors.filter(e => e.property?.includes('address'));
455
+ expect(addressErrors.length).toBeGreaterThan(0);
456
+ });
457
+
458
+ it('should properly serialize person with address to JSON', () => {
459
+ const personData = {
460
+ firstName: 'Alice',
461
+ lastName: 'Johnson',
462
+ email: 'alice@example.com',
463
+ age: 28,
464
+ phoneNumber: '444-555-6666',
465
+ isActive: true,
466
+ address: validAddress
467
+ };
468
+
469
+ const person = Person.fromJSON(personData);
470
+ const json = person.toJSON();
471
+
472
+ expect(json).toHaveProperty('address');
473
+ expect(json.address).toHaveProperty('street', '456 Elm Avenue');
474
+ expect(json.address).toHaveProperty('zipCode', '67890');
475
+ expect(json.address).toHaveProperty('country', 'Canada');
476
+ });
477
+ });
478
+
479
+ describe('Company with Address (SharedComposition)', () => {
480
+ let validCEO: Person;
481
+ let validAddress: Address;
482
+
483
+ beforeEach(() => {
484
+ const ceoData = {
485
+ firstName: 'Robert',
486
+ lastName: 'Johnson',
487
+ email: 'robert@company.com',
488
+ age: 45,
489
+ phoneNumber: '+1987654321',
490
+ additionalInfo: '<p>CEO and Founder</p>',
491
+ isActive: true
492
+ };
493
+ validCEO = Person.fromJSON(ceoData);
494
+
495
+ const addressData = {
496
+ street: '100 Corporate Plaza',
497
+ zipCode: '98765',
498
+ country: 'United States'
499
+ };
500
+ validAddress = Address.fromJSON(addressData);
501
+ });
502
+
503
+ it('should validate a company with shared address composition', async () => {
504
+ const companyData = {
505
+ name: "TechCorp Solutions",
506
+ ceo: validCEO,
507
+ address: validAddress
508
+ };
509
+
510
+ const company = Company.fromJSON(companyData);
511
+ const errors = await company.validate();
512
+
513
+ expect(errors.length).toBe(0);
514
+ expect(company.address).toBeDefined();
515
+ expect(company.address.street).toBe('100 Corporate Plaza');
516
+ expect(company.address.zipCode).toBe('98765');
517
+ expect(company.address.country).toBe('United States');
518
+ });
519
+
520
+ it('should validate a company without address (optional shared composition)', async () => {
521
+ const companyData = {
522
+ name: "StartupCorp",
523
+ ceo: validCEO
524
+ // address is optional
525
+ };
526
+
527
+ const company = Company.fromJSON(companyData);
528
+ const errors = await company.validate();
529
+
530
+ expect(errors.length).toBe(0);
531
+ expect(company.address).toBeUndefined();
532
+ });
533
+
534
+ it('should fail validation when company has invalid address', async () => {
535
+ const invalidAddressData = {
536
+ street: '200 Business Street'
537
+ // zipCode and country are missing, making address invalid
538
+ };
539
+ const invalidAddress = Address.fromJSON(invalidAddressData);
540
+
541
+ const companyData = {
542
+ name: "FailCorp",
543
+ ceo: validCEO,
544
+ address: invalidAddress
545
+ };
546
+
547
+ const company = Company.fromJSON(companyData);
548
+ const errors = await company.validate();
549
+
550
+ expect(errors.length).toBeGreaterThan(0);
551
+ // Should contain nested validation errors from address
552
+ const addressErrors = errors.filter(e => e.property?.includes('address'));
553
+ expect(addressErrors.length).toBeGreaterThan(0);
554
+ });
555
+
556
+ it('should properly serialize company with address to JSON', () => {
557
+ const companyData = {
558
+ name: "GlobalTech Inc",
559
+ ceo: validCEO,
560
+ address: validAddress
561
+ };
562
+
563
+ const company = Company.fromJSON(companyData);
564
+ const json = company.toJSON();
565
+
566
+ expect(json).toHaveProperty('address');
567
+ expect(json.address).toHaveProperty('street', '100 Corporate Plaza');
568
+ expect(json.address).toHaveProperty('zipCode', '98765');
569
+ expect(json.address).toHaveProperty('country', 'United States');
570
+ });
571
+ });
572
+
573
+ describe('Full Integration Tests', () => {
574
+ it('should handle complete model relationships', async () => {
575
+ // Create a CEO
576
+ const ceoData = {
577
+ firstName: 'John',
578
+ lastName: 'Smith',
579
+ email: 'john.smith@techcorp.com',
580
+ age: 45,
581
+ phoneNumber: '+1555123456',
582
+ additionalInfo: '<p>Experienced technology leader</p>',
583
+ isActive: true
584
+ };
585
+ const ceo = Person.fromJSON(ceoData);
586
+
587
+ // Create a company with the CEO (composition)
588
+ const companyData = {
589
+ name: "TechCorp Innovation Labs",
590
+ ceo: ceo
591
+ };
592
+ const company = Company.fromJSON(companyData);
593
+
594
+ // Create an app that references the company
595
+ const appData = {
596
+ name: "InnovationPlatform",
597
+ version: "03.01.05",
598
+ description: "A cutting-edge platform for technological innovation and development",
599
+ ownerCompany: company
600
+ };
601
+ const app = App.fromJSON(appData);
602
+
603
+ // Validate all models
604
+ const ceoErrors = await ceo.validate();
605
+ const companyErrors = await company.validate();
606
+ const appErrors = await app.validate();
607
+
608
+ expect(ceoErrors.length).toBe(0);
609
+ expect(companyErrors.length).toBe(0);
610
+ expect(appErrors.length).toBe(0);
611
+
612
+ // Verify relationships
613
+ expect(app.ownerCompany.name).toBe("TechCorp Innovation Labs");
614
+ expect(app.ownerCompany.ceo.firstName).toBe("John");
615
+ expect(company.ceo.email).toBe("john.smith@techcorp.com");
616
+ });
617
+
618
+ it('should properly serialize complete model hierarchy', () => {
619
+ const ceoData = {
620
+ firstName: 'Alice',
621
+ lastName: 'Wilson',
622
+ email: 'alice@startup.com',
623
+ age: 35,
624
+ phoneNumber: '+1444555666',
625
+ isActive: true
626
+ };
627
+ const ceo = Person.fromJSON(ceoData);
628
+
629
+ const companyData = {
630
+ name: "StartupCorp",
631
+ ceo: ceo
632
+ };
633
+ const company = Company.fromJSON(companyData);
634
+
635
+ const appData = {
636
+ name: "StartupApp",
637
+ version: "01.00.01",
638
+ description: "A revolutionary startup application",
639
+ ownerCompany: company
640
+ };
641
+ const app = App.fromJSON(appData);
642
+
643
+ // Serialize to JSON
644
+ const appJson = app.toJSON();
645
+
646
+ // Verify the JSON structure
647
+ expect(appJson).toHaveProperty('name', 'StartupApp');
648
+ expect(appJson).toHaveProperty('version', '01.00.01');
649
+ expect(appJson).toHaveProperty('ownerCompany');
650
+ expect(appJson.ownerCompany).toHaveProperty('name', 'StartupCorp');
651
+ expect(appJson.ownerCompany).toHaveProperty('ceo');
652
+ expect(appJson.ownerCompany.ceo).toHaveProperty('firstName', 'Alice');
653
+ });
654
+ });
655
655
  });