@solidxai/core 0.1.11-beta.1 → 0.1.11-beta.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 (136) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/CLAUDE.md +26 -0
  3. package/dist/controllers/datasource-introspection.controller.d.ts +38 -1
  4. package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -1
  5. package/dist/controllers/datasource-introspection.controller.js +38 -0
  6. package/dist/controllers/datasource-introspection.controller.js.map +1 -1
  7. package/dist/controllers/service.controller.d.ts +1 -0
  8. package/dist/controllers/service.controller.d.ts.map +1 -1
  9. package/dist/controllers/service.controller.js +2 -2
  10. package/dist/controllers/service.controller.js.map +1 -1
  11. package/dist/dtos/datasource-introspection-mapping.dto.d.ts +4 -0
  12. package/dist/dtos/datasource-introspection-mapping.dto.d.ts.map +1 -1
  13. package/dist/dtos/datasource-introspection-mapping.dto.js +18 -2
  14. package/dist/dtos/datasource-introspection-mapping.dto.js.map +1 -1
  15. package/dist/entities/{legacy-common-with-id.entity.d.ts → legacy-common-with-generated-id.entity.d.ts} +2 -2
  16. package/dist/entities/legacy-common-with-generated-id.entity.d.ts.map +1 -0
  17. package/dist/entities/{legacy-common-with-id.entity.js → legacy-common-with-generated-id.entity.js} +4 -4
  18. package/dist/entities/legacy-common-with-generated-id.entity.js.map +1 -0
  19. package/dist/entities/{legacy-common.entity.d.ts → legacy-common.entity-with-existing-id.entity.d.ts} +1 -1
  20. package/dist/entities/legacy-common.entity-with-existing-id.entity.d.ts.map +1 -0
  21. package/dist/entities/{legacy-common.entity.js → legacy-common.entity-with-existing-id.entity.js} +1 -1
  22. package/dist/entities/legacy-common.entity-with-existing-id.entity.js.map +1 -0
  23. package/dist/helpers/field-crud-managers/IntFieldCrudManager.d.ts.map +1 -1
  24. package/dist/helpers/field-crud-managers/IntFieldCrudManager.js +4 -1
  25. package/dist/helpers/field-crud-managers/IntFieldCrudManager.js.map +1 -1
  26. package/dist/helpers/model-metadata-helper.service.js +3 -3
  27. package/dist/helpers/model-metadata-helper.service.js.map +1 -1
  28. package/dist/helpers/solid-registry.d.ts +2 -1
  29. package/dist/helpers/solid-registry.d.ts.map +1 -1
  30. package/dist/helpers/solid-registry.js.map +1 -1
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/interfaces/datasource-introspection-provider.interface.d.ts +1 -0
  36. package/dist/interfaces/datasource-introspection-provider.interface.d.ts.map +1 -1
  37. package/dist/interfaces/datasource-introspection-provider.interface.js.map +1 -1
  38. package/dist/seeders/module-metadata-seeder.service.d.ts +5 -1
  39. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  40. package/dist/seeders/module-metadata-seeder.service.js +61 -3
  41. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  42. package/dist/seeders/seed-data/solid-core-metadata.json +19 -3
  43. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts.map +1 -1
  44. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js +6 -5
  45. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js.map +1 -1
  46. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.d.ts.map +1 -1
  47. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js +12 -0
  48. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js.map +1 -1
  49. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts.map +1 -1
  50. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js +2 -0
  51. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js.map +1 -1
  52. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts.map +1 -1
  53. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js +28 -15
  54. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js.map +1 -1
  55. package/dist/services/datasource-introspection.service.d.ts +52 -2
  56. package/dist/services/datasource-introspection.service.d.ts.map +1 -1
  57. package/dist/services/datasource-introspection.service.js +534 -160
  58. package/dist/services/datasource-introspection.service.js.map +1 -1
  59. package/dist/services/locale.service.d.ts +14 -0
  60. package/dist/services/locale.service.d.ts.map +1 -1
  61. package/dist/services/locale.service.js +83 -0
  62. package/dist/services/locale.service.js.map +1 -1
  63. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +10 -10
  64. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
  65. package/dist/services/role-metadata.service.d.ts +1 -0
  66. package/dist/services/role-metadata.service.d.ts.map +1 -1
  67. package/dist/services/role-metadata.service.js +23 -2
  68. package/dist/services/role-metadata.service.js.map +1 -1
  69. package/dist/services/view-metadata.service.d.ts.map +1 -1
  70. package/dist/services/view-metadata.service.js +4 -7
  71. package/dist/services/view-metadata.service.js.map +1 -1
  72. package/dist/subscribers/audit.subscriber.d.ts +2 -0
  73. package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
  74. package/dist/subscribers/audit.subscriber.js +25 -6
  75. package/dist/subscribers/audit.subscriber.js.map +1 -1
  76. package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
  77. package/dist/subscribers/computed-entity-field.subscriber.js +1 -0
  78. package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
  79. package/dist/subscribers/created-by-updated-by.subscriber.d.ts.map +1 -1
  80. package/dist/subscribers/created-by-updated-by.subscriber.js.map +1 -1
  81. package/dist/subscribers/field-metadata.subscriber.d.ts.map +1 -1
  82. package/dist/subscribers/field-metadata.subscriber.js.map +1 -1
  83. package/package.json +1 -1
  84. package/src/controllers/datasource-introspection.controller.ts +27 -1
  85. package/src/controllers/service.controller.ts +3 -2
  86. package/src/dtos/datasource-introspection-mapping.dto.ts +10 -0
  87. package/src/entities/{legacy-common-with-id.entity.ts → legacy-common-with-generated-id.entity.ts} +1 -1
  88. package/src/helpers/field-crud-managers/IntFieldCrudManager.ts +4 -1
  89. package/src/helpers/model-metadata-helper.service.ts +1 -1
  90. package/src/helpers/solid-registry.ts +7 -1
  91. package/src/index.ts +2 -2
  92. package/src/interfaces/datasource-introspection-provider.interface.ts +1 -0
  93. package/src/seeders/module-metadata-seeder.service.ts +72 -2
  94. package/src/seeders/seed-data/solid-core-metadata.json +19 -3
  95. package/src/services/1.js +6 -0
  96. package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +10 -5
  97. package/src/services/datasource-introspection/mssql-datasource-introspection-provider.service.ts +12 -0
  98. package/src/services/datasource-introspection/mysql-datasource-introspection-provider.service.ts +2 -0
  99. package/src/services/datasource-introspection/postgres-datasource-introspection-provider.service.ts +28 -15
  100. package/src/services/datasource-introspection.service.ts +626 -136
  101. package/src/services/locale.service.ts +111 -3
  102. package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +2 -2
  103. package/src/services/role-metadata.service.ts +26 -2
  104. package/src/services/view-metadata.service.ts +4 -8
  105. package/src/subscribers/audit.subscriber.ts +34 -14
  106. package/src/subscribers/computed-entity-field.subscriber.ts +1 -0
  107. package/src/subscribers/created-by-updated-by.subscriber.ts +3 -0
  108. package/src/subscribers/field-metadata.subscriber.ts +3 -0
  109. package/dist/entities/legacy-common-with-id.entity.d.ts.map +0 -1
  110. package/dist/entities/legacy-common-with-id.entity.js.map +0 -1
  111. package/dist/entities/legacy-common.entity.d.ts.map +0 -1
  112. package/dist/entities/legacy-common.entity.js.map +0 -1
  113. package/dist-tests/api/authenticate.spec.js +0 -119
  114. package/dist-tests/api/authenticate.spec.js.map +0 -1
  115. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +0 -97
  116. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +0 -1
  117. package/dist-tests/api/ping.spec.js +0 -21
  118. package/dist-tests/api/ping.spec.js.map +0 -1
  119. package/dist-tests/helpers/auth.js +0 -41
  120. package/dist-tests/helpers/auth.js.map +0 -1
  121. package/dist-tests/helpers/env.js +0 -11
  122. package/dist-tests/helpers/env.js.map +0 -1
  123. package/docs/agent-hub-grooming.md +0 -301
  124. package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +0 -438
  125. package/docs/dashboards/dashboard-curl-smoke-tests.txt +0 -146
  126. package/docs/dashboards/delete-legacy-dashboard-metadata.sql +0 -172
  127. package/docs/datasource-introspection-ddl-analysis.md +0 -326
  128. package/docs/datasource-introspection-implementation-plan.md +0 -306
  129. package/docs/grouping-enhancements.md +0 -89
  130. package/docs/java-spring/README.md +0 -3
  131. package/docs/java-spring/solid-core-module-deep-dive-report.md +0 -1317
  132. package/docs/module-package-import-handoff.md +0 -691
  133. package/docs/seed-changes.md +0 -65
  134. package/docs/test-data-workflow.md +0 -200
  135. package/docs/type-declaration-import-issue.md +0 -24
  136. /package/src/entities/{legacy-common.entity.ts → legacy-common.entity-with-existing-id.entity.ts} +0 -0
@@ -1,7 +1,7 @@
1
- import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
1
+ import { BadRequestException, Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
2
2
  import { ModuleRef } from "@nestjs/core";
3
3
  import { InjectEntityManager } from '@nestjs/typeorm';
4
- import { EntityManager } from 'typeorm';
4
+ import { EntityManager, In, IsNull, Not } from 'typeorm';
5
5
 
6
6
  import { Locale } from 'src/entities/locale.entity';
7
7
  import { SolidRegistry } from 'src/helpers/solid-registry';
@@ -31,7 +31,115 @@ export class LocaleService extends CRUDService<Locale> implements OnApplicationB
31
31
  // Load the security rules from the database
32
32
  this.loadLocales();
33
33
  }
34
- async loadLocales() {
34
+
35
+ private async assertCanSetAsDefault(currentLocaleId?: number) {
36
+ const defaultLocales = await this.repo.find({
37
+ where: {
38
+ isDefault: true,
39
+ } as any,
40
+ });
41
+
42
+ const conflictingDefaultLocale = defaultLocales.find(
43
+ (locale) => locale.id !== currentLocaleId
44
+ );
45
+
46
+ if (conflictingDefaultLocale) {
47
+ throw new BadRequestException(
48
+ `Locale "${conflictingDefaultLocale.displayName}" is already marked as default. Mark it as non-default first.`
49
+ );
50
+ }
51
+ }
52
+
53
+ override async create(createDto: any, files: Express.Multer.File[] = [], solidRequestContext: any = {}): Promise<Locale> {
54
+ if (createDto?.isDefault === true) {
55
+ await this.assertCanSetAsDefault();
56
+ }
57
+
58
+ const savedLocale = await super.create(createDto, files, solidRequestContext);
59
+ await this.loadLocales();
60
+ return savedLocale;
61
+ }
62
+
63
+ override async insertMany(createDtos: any[], filesArray: Express.Multer.File[][] = [], solidRequestContext: any = {}): Promise<Locale[]> {
64
+ const defaultLocalesInPayload = (createDtos || []).filter((dto) => dto?.isDefault === true);
65
+
66
+ if (defaultLocalesInPayload.length > 1) {
67
+ throw new BadRequestException(`Only one locale can be marked as default.`);
68
+ }
69
+
70
+ if (defaultLocalesInPayload.length === 1) {
71
+ await this.assertCanSetAsDefault();
72
+ }
73
+
74
+ const savedLocales = await super.insertMany(createDtos, filesArray, solidRequestContext);
75
+ await this.loadLocales();
76
+ return savedLocales;
77
+ }
78
+
79
+ override async update(id: number, updateDto: any, files: Express.Multer.File[] = [], isPartialUpdate: boolean = false, solidRequestContext: any = {}, isUpdate: boolean = false): Promise<Locale> {
80
+ if (updateDto?.isDefault === true) {
81
+ await this.assertCanSetAsDefault(id);
82
+ }
83
+
84
+ const savedLocale = await super.update(id, updateDto, files, isPartialUpdate, solidRequestContext, isUpdate);
85
+ await this.loadLocales();
86
+ return savedLocale;
87
+ }
88
+
89
+ override async recover(id: number, solidRequestContext: any = {}) {
90
+ const localeToRecover = await this.repo.findOne({
91
+ where: {
92
+ id,
93
+ deletedAt: Not(IsNull()),
94
+ } as any,
95
+ withDeleted: true,
96
+ });
97
+
98
+ if (localeToRecover?.isDefault) {
99
+ await this.assertCanSetAsDefault(id);
100
+ }
101
+
102
+ const recoveredLocale = await super.recover(id, solidRequestContext);
103
+ await this.loadLocales();
104
+ return recoveredLocale;
105
+ }
106
+
107
+ override async recoverMany(ids: number[], solidRequestContext: any = {}) {
108
+ const localesToRecover = await this.repo.find({
109
+ where: {
110
+ id: In(ids),
111
+ deletedAt: Not(IsNull()),
112
+ } as any,
113
+ withDeleted: true,
114
+ });
115
+
116
+ const defaultLocalesToRecover = localesToRecover.filter((locale) => locale.isDefault);
117
+ if (defaultLocalesToRecover.length > 1) {
118
+ throw new BadRequestException(`Only one locale can be marked as default.`);
119
+ }
120
+
121
+ if (defaultLocalesToRecover.length === 1) {
122
+ await this.assertCanSetAsDefault(defaultLocalesToRecover[0].id);
123
+ }
124
+
125
+ const recoveredLocales = await super.recoverMany(ids, solidRequestContext);
126
+ await this.loadLocales();
127
+ return recoveredLocales;
128
+ }
129
+
130
+ override async delete(id: number, solidRequestContext: any = {}) {
131
+ const deletedLocale = await super.delete(id, solidRequestContext);
132
+ await this.loadLocales();
133
+ return deletedLocale;
134
+ }
135
+
136
+ override async deleteMany(ids: number[], solidRequestContext: any = {}) {
137
+ const deletedLocales = await super.deleteMany(ids, solidRequestContext);
138
+ await this.loadLocales();
139
+ return deletedLocales;
140
+ }
141
+
142
+ async loadLocales() {
35
143
  const locales = await this.repo.find();
36
144
  this.logger.debug(`Loaded ${locales.length} locales into registry`);
37
145
  this.solidRegistry.registerlocales(locales);
@@ -2,8 +2,8 @@ import { Injectable, Logger } from "@nestjs/common";
2
2
  import { ConfigService } from "@nestjs/config";
3
3
  import { CommonEntity } from "src/entities/common.entity";
4
4
  import { FieldMetadata } from "src/entities/field-metadata.entity";
5
- import { LegacyCommonEntityWithExistingId } from "src/entities/legacy-common.entity";
6
- import { LegacyCommonEntityWithGeneratedId } from "src/entities/legacy-common-with-id.entity";
5
+ import { LegacyCommonEntityWithExistingId } from "src/entities/legacy-common.entity-with-existing-id.entity";
6
+ import { LegacyCommonEntityWithGeneratedId } from "src/entities/legacy-common-with-generated-id.entity";
7
7
  import { Media } from "src/entities/media.entity";
8
8
  import { MediaStorageProvider } from "src/interfaces";
9
9
  import { DiskFileService, S3FileService } from "src/services/file";
@@ -8,6 +8,7 @@ import { ERROR_MESSAGES } from 'src/constants/error-messages';
8
8
  import { PermissionMetadataRepository } from 'src/repository/permission-metadata.repository';
9
9
  import { RoleMetadataRepository } from 'src/repository/role-metadata.repository';
10
10
  import { CreateRoleMetadataDto } from '../dtos/create-role-metadata.dto';
11
+ import { ModuleMetadata } from '../entities/module-metadata.entity';
11
12
  import { PermissionMetadata } from '../entities/permission-metadata.entity';
12
13
  import { RoleMetadata } from '../entities/role-metadata.entity';
13
14
 
@@ -65,13 +66,16 @@ export class RoleMetadataService extends CRUDService<RoleMetadata> {
65
66
  for (let id = 0; id < roles.length; id++) {
66
67
  try {
67
68
  const roleObj = roles[id];
69
+ const module = await this.resolveRoleModule(roleObj);
68
70
  // this.logger.log(`Resolving role: ${JSON.stringify(roleObj)}`);
69
71
 
70
72
  const existingRole = await this.repo.findOne({
71
73
  where: {
72
74
  name: roleObj.name,
73
75
  },
74
- relations: {},
76
+ relations: {
77
+ module: true,
78
+ },
75
79
  });
76
80
 
77
81
  // Create only if not existing already.
@@ -87,9 +91,13 @@ export class RoleMetadataService extends CRUDService<RoleMetadata> {
87
91
  }
88
92
 
89
93
  // const role = this.repo.create({ ...roleObj, permissions });
90
- const role = this.repo.create({ ...roleObj });
94
+ const role = this.repo.create({ ...roleObj, ...(module ? { module } : {}) });
91
95
  await this.repo.save(role);
92
96
  } else {
97
+ if (module && existingRole.module?.id !== module.id) {
98
+ existingRole.module = module;
99
+ await this.repo.save(existingRole);
100
+ }
93
101
  /*
94
102
  this.logger.debug(`Role ${roleObj.name} already exists`);
95
103
  const existingPermissions = existingRole.permissions.map(permission => permission.name);
@@ -112,6 +120,22 @@ export class RoleMetadataService extends CRUDService<RoleMetadata> {
112
120
  }
113
121
  }
114
122
 
123
+ private async resolveRoleModule(roleObj: CreateRoleMetadataDto): Promise<ModuleMetadata | null> {
124
+ if (roleObj.moduleId) {
125
+ return await this.entityManager.getRepository(ModuleMetadata).findOne({
126
+ where: { id: roleObj.moduleId },
127
+ });
128
+ }
129
+
130
+ if (roleObj.moduleUserKey) {
131
+ return await this.entityManager.getRepository(ModuleMetadata).findOne({
132
+ where: { name: roleObj.moduleUserKey },
133
+ });
134
+ }
135
+
136
+ return null;
137
+ }
138
+
115
139
  async addAllPermissionsToRole(roleName: string): Promise<RoleMetadata> {
116
140
  return await this._addPermissionsToRole(roleName, null);
117
141
  }
@@ -151,15 +151,11 @@ export class ViewMetadataService extends CRUDService<ViewMetadata> {
151
151
  return { records: [], defaultEntityLocaleId: null };
152
152
  }
153
153
 
154
- const defaultLocale = await this.entityManager.getRepository(Locale).findOne({ where: { isDefault: true } });
155
-
156
- let defaultEntityLocaleId: string;
157
- if (entityRecord.localeName === defaultLocale?.locale) {
158
- defaultEntityLocaleId = entityRecord.id;
159
- this.logger.debug(`Editing default locale record with id ${defaultEntityLocaleId}`);
154
+ const defaultEntityLocaleId = entityRecord.defaultEntityLocaleId || entityRecord.id;
155
+ if (entityRecord.defaultEntityLocaleId) {
156
+ this.logger.debug(`Editing translated locale record. Translation root id: ${defaultEntityLocaleId}`);
160
157
  } else {
161
- defaultEntityLocaleId = entityRecord.defaultEntityLocaleId;
162
- this.logger.debug(`Editing non-default locale record. DefaultEntityLocaleId: ${defaultEntityLocaleId}`);
158
+ this.logger.debug(`Editing translation root record with id ${defaultEntityLocaleId}`);
163
159
  }
164
160
 
165
161
  const records = await currentEntityRepository.find({
@@ -84,24 +84,44 @@ export class AuditSubscriber implements EntitySubscriberInterface {
84
84
  const batch = this.perTxn.get(event.queryRunner) ?? [];
85
85
  this.perTxn.delete(event.queryRunner);
86
86
 
87
- // Now outside the DB transaction — safe to publish to the queue.
88
- // allSettled: publish in parallel; a single failure does not block the rest.
89
- const results = await Promise.allSettled(
90
- batch.map(payload => this.publisherFactory.publish({ payload }, 'ChatterQueuePublisher'))
91
- );
92
-
93
- results.forEach((result, i) => {
94
- if (result.status === 'rejected') {
95
- this.logger.error(
96
- `Failed to publish audit event for ${batch[i].modelName}#${batch[i].entityId}`,
97
- result.reason,
98
- );
99
- }
100
- });
87
+ if (batch.length === 0) return;
88
+
89
+ if (this.shouldDeferAuditPublish()) {
90
+ setImmediate(() => void this.publishAuditBatch(batch));
91
+ return;
92
+ }
93
+
94
+ await this.publishAuditBatch(batch);
101
95
  }
102
96
 
103
97
  afterTransactionRollback(event: { queryRunner: any }) {
104
98
  // Drop buffered payloads; the write never happened.
105
99
  this.perTxn.delete(event.queryRunner);
106
100
  }
101
+
102
+ private shouldDeferAuditPublish(): boolean {
103
+ const driver = (process.env.DEFAULT_DATABASE_DRIVER ?? '').toLowerCase();
104
+ const poolMax = Number(process.env.DEFAULT_DATABASE_POOL_MAX ?? 20);
105
+ return driver === 'pglite' && Number.isFinite(poolMax) && poolMax <= 1;
106
+ }
107
+
108
+ private async publishAuditBatch(batch: AuditQueuePayload[]) {
109
+ try {
110
+ // allSettled: publish in parallel; a single failure does not block the rest.
111
+ const results = await Promise.allSettled(
112
+ batch.map(payload => this.publisherFactory.publish({ payload }, 'ChatterQueuePublisher'))
113
+ );
114
+
115
+ results.forEach((result, i) => {
116
+ if (result.status === 'rejected') {
117
+ this.logger.error(
118
+ `Failed to publish audit event for ${batch[i].modelName}#${batch[i].entityId}`,
119
+ result.reason,
120
+ );
121
+ }
122
+ });
123
+ } catch (error) {
124
+ this.logger.error('Failed to publish audit batch', error);
125
+ }
126
+ }
107
127
  }
@@ -175,6 +175,7 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
175
175
  const base: TypeOrmEventContext = {
176
176
  metadataName: event.metadata?.name,
177
177
  eventType: eventType,
178
+ manager: event.manager,
178
179
  };
179
180
  if ("entityId" in event && event.entityId) {
180
181
  base.entityId = event.entityId;
@@ -38,6 +38,9 @@ export class CreatedByUpdatedBySubscriber implements EntitySubscriberInterface {
38
38
  return;
39
39
  }
40
40
 
41
+ // NOTE: If re-enabling loadUser below, query via `event.queryRunner.manager`, NOT
42
+ // `this.defaultDataSource.getRepository(...)`. A second connection opened mid-transaction
43
+ // deadlocks single-threaded engines (PGlite).
41
44
  // const loadedUser = await this.loadUser(activeUserOrUndefined as unknown as ActiveUserData);
42
45
  // if (isInsert) {
43
46
  // event.entity.createdBy = loadedUser?.id;
@@ -21,6 +21,9 @@ export class FieldMetadataSubscriber implements EntitySubscriberInterface<FieldM
21
21
  }
22
22
 
23
23
  async afterUpdate(event: UpdateEvent<FieldMetadata>): Promise<void> {
24
+ // NOTE: If re-enabling the block below, use `event.queryRunner.manager` (the active
25
+ // transaction's connection), NOT `this.dataSource.getRepository(...)`. A second
26
+ // connection opened here mid-transaction deadlocks single-threaded engines (PGlite).
24
27
  // If a field of type relation.many-to-one is marked for removal, and this field had an inverse relation created
25
28
  // It means there is a field of type relation.one-to-many in the co-model, which needs to be removed
26
29
  // if (event.entity && event.entity.isMarkedForRemoval && event.entity.type === 'relation' && event.entity.relationType === 'many-to-one' && event.entity.relationCreateInverse === true) {
@@ -1 +0,0 @@
1
- {"version":3,"file":"legacy-common-with-id.entity.d.ts","sourceRoot":"","sources":["../../src/entities/legacy-common-with-id.entity.ts"],"names":[],"mappings":"AAEA,OAAO,EAA8B,gCAAgC,EAAE,MAAM,wBAAwB,CAAC;AAEtG,8BACsB,iCAAkC,SAAQ,gCAAgC;IAI5F,EAAE,EAAE,MAAM,CAAA;CACb"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"legacy-common-with-id.entity.js","sourceRoot":"","sources":["../../src/entities/legacy-common-with-id.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,yDAAoD;AACpD,qCAA4C;AAC5C,iEAAsG;AAG/F,IAAe,iCAAiC,GAAhD,MAAe,iCAAkC,SAAQ,uDAAgC;;;;CAK/F,CAAA;AALqB,8EAAiC;AAInD;IAHC,IAAA,0BAAM,GAAE;IACR,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,iDAA0B,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACnF,IAAA,mBAAS,EAAC,WAAW,CAAC;;6DACb;4CAJQ,iCAAiC;IADtD,IAAA,2BAAO,GAAE;GACY,iCAAiC,CAKtD","sourcesContent":["import { Exclude, Expose } from \"class-transformer\";\nimport { Column, Generated } from \"typeorm\";\nimport { LEGACY_TABLE_FIELDS_PREFIX, LegacyCommonEntityWithExistingId } from \"./legacy-common.entity\";\n\n@Exclude()\nexport abstract class LegacyCommonEntityWithGeneratedId extends LegacyCommonEntityWithExistingId {\n @Expose()\n @Column({ type: 'integer', name: `${LEGACY_TABLE_FIELDS_PREFIX}_id`, unique: true })\n @Generated(\"increment\")\n id: number\n}"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"legacy-common.entity.d.ts","sourceRoot":"","sources":["../../src/entities/legacy-common.entity.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,8BACsB,gCAAgC;IAOlD,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAGvB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAIvB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAGvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAI9B,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IAIzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAI1B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IAgBrC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAIzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"legacy-common.entity.js","sourceRoot":"","sources":["../../src/entities/legacy-common.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,yDAAoD;AACpD,qGAAgG;AAChG,qCAA8F;AAEjF,QAAA,0BAA0B,GAAG,IAAI,CAAC;AAGxC,IAAe,gCAAgC,GAA/C,MAAe,gCAAgC;;;;CAkDrD,CAAA;AAlDqB,4EAAgC;AAOlD;IADC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,GAAG,kCAA0B,aAAa,EAAE,WAAW,EAAE,sDAAwB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;8BACnH,IAAI;mEAAQ;AAGvB;IADC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,GAAG,kCAA0B,aAAa,EAAE,WAAW,EAAE,sDAAwB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;8BACnH,IAAI;mEAAQ;AAIvB;IAFC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,GAAG,kCAA0B,aAAa,EAAE,WAAW,EAAE,sDAAwB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC7H,IAAA,eAAK,GAAE;8BACG,IAAI;mEAAQ;AAGvB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,GAAG,kCAA0B,kBAAkB,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;wEAC5E;AAI9B;IAFC,IAAA,0BAAM,GAAE;IACR,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,GAAG,kCAA0B,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,sDAAwB,EAAE,CAAC;8BACxH,IAAI;qEAAQ;AAIzB;IAFC,IAAA,0BAAM,GAAE;IACR,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,kCAA0B,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;oEACpF;AAI1B;IAFC,IAAA,0BAAM,GAAE;IACR,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,kCAA0B,2BAA2B,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+EAClF;AAgBrC;IAFC,IAAA,0BAAM,GAAE;IACR,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,GAAG,kCAA0B,gBAAgB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mEACvD;AAIzB;IAFC,IAAA,0BAAM,GAAE;IACR,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,GAAG,kCAA0B,gBAAgB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mEACvD;2CAjDP,gCAAgC;IADrD,IAAA,2BAAO,GAAE;GACY,gCAAgC,CAkDrD","sourcesContent":["import { Exclude, Expose } from \"class-transformer\";\nimport { LocalDateTimeTransformer } from \"src/transformers/typeorm/local-date-time-transformer\";\nimport { Column, CreateDateColumn, DeleteDateColumn, Index, UpdateDateColumn } from \"typeorm\";\n\nexport const LEGACY_TABLE_FIELDS_PREFIX = 'ss';\n\n@Exclude()\nexport abstract class LegacyCommonEntityWithExistingId {\n // @Expose()\n // @Column({ type: 'integer', name: `${LEGACY_TABLE_FIELDS_PREFIX}_id`, unique: true })\n // @Generated(\"increment\")\n // id: number\n\n @CreateDateColumn({ name: `${LEGACY_TABLE_FIELDS_PREFIX}_created_at`, transformer: LocalDateTimeTransformer, nullable: true })\n createdAt: Date | null;\n\n @UpdateDateColumn({ name: `${LEGACY_TABLE_FIELDS_PREFIX}_updated_at`, transformer: LocalDateTimeTransformer, nullable: true })\n updatedAt: Date | null;\n\n @DeleteDateColumn({ name: `${LEGACY_TABLE_FIELDS_PREFIX}_deleted_at`, transformer: LocalDateTimeTransformer, nullable: true })\n @Index()\n deletedAt: Date | null;\n\n @Column({ name: `${LEGACY_TABLE_FIELDS_PREFIX}_deleted_tracker`, default: \"not-deleted\", nullable: true })\n deletedTracker: string | null;\n\n @Expose()\n @Column({ name: `${LEGACY_TABLE_FIELDS_PREFIX}_published_at`, default: null, nullable: true, transformer: LocalDateTimeTransformer })\n publishedAt: Date | null;\n\n @Expose()\n @Column({ type: \"varchar\", name: `${LEGACY_TABLE_FIELDS_PREFIX}_locale_name`, default: null, nullable: true })\n localeName: string | null;\n\n @Expose()\n @Column({ type: \"int\", name: `${LEGACY_TABLE_FIELDS_PREFIX}_default_entity_locale_id`, default: null, nullable: true })\n defaultEntityLocaleId: number | null;\n\n // @Expose()\n // @Type( () => require('./user.entity').User?.default ?? require('./user.entity').User )\n // @ManyToOne(() => require('./user.entity').User?.default ?? require('./user.entity').User, { nullable: true })\n // @JoinColumn({ name: `${LEGACY_TABLE_FIELDS_PREFIX}_created_by_id` })\n // createdBy: User;\n\n // @Expose()\n // @Type( () => require('./user.entity').User?.default ?? require('./user.entity').User )\n // @ManyToOne(() => require('./user.entity').User?.default ?? require('./user.entity').User, { nullable: true })\n // @JoinColumn({ name: `${LEGACY_TABLE_FIELDS_PREFIX}_updated_by_id` })\n // updatedBy: User; \n\n @Expose()\n @Column({ name: `${LEGACY_TABLE_FIELDS_PREFIX}_created_by_id`, nullable: true })\n createdBy: number | null;\n\n @Expose()\n @Column({ name: `${LEGACY_TABLE_FIELDS_PREFIX}_updated_by_id`, nullable: true })\n updatedBy: number | null;\n}\n"]}
@@ -1,119 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const test_1 = require("@playwright/test");
4
- const env_1 = require("../helpers/env");
5
- const baseURL = process.env.API_BASE_URL ?? "http://localhost:3000";
6
- const TEST_USER_EMAIL = (0, env_1.getRequiredEnv)("TEST_USER_EMAIL");
7
- const TEST_USER_PASSWORD = (0, env_1.getRequiredEnv)("TEST_USER_PASSWORD");
8
- function base64UrlDecode(input) {
9
- const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
10
- const padded = normalized.length % 4 === 0
11
- ? normalized
12
- : normalized.padEnd(normalized.length + (4 - (normalized.length % 4)), "=");
13
- return Buffer.from(padded, "base64").toString("utf-8");
14
- }
15
- function validateJwt(token) {
16
- const parts = token.split(".");
17
- if (parts.length !== 3) {
18
- throw new Error("JWT must have three dot-separated parts.");
19
- }
20
- const headerJson = JSON.parse(base64UrlDecode(parts[0]));
21
- const payloadJson = JSON.parse(base64UrlDecode(parts[1]));
22
- if (!headerJson || typeof headerJson !== "object") {
23
- throw new Error("JWT header must be a JSON object.");
24
- }
25
- if (!payloadJson || typeof payloadJson !== "object") {
26
- throw new Error("JWT payload must be a JSON object.");
27
- }
28
- if (typeof payloadJson.exp !== "number") {
29
- throw new Error("JWT payload.exp must be a number.");
30
- }
31
- return payloadJson;
32
- }
33
- (0, test_1.test)("API: authenticate succeeds with valid credentials", async () => {
34
- const api = await test_1.request.newContext({
35
- baseURL,
36
- extraHTTPHeaders: {
37
- accept: "*/*",
38
- "content-type": "application/json",
39
- },
40
- });
41
- try {
42
- const res = await api.post("/api/iam/authenticate", {
43
- data: {
44
- email: TEST_USER_EMAIL,
45
- username: "",
46
- password: TEST_USER_PASSWORD,
47
- },
48
- });
49
- (0, test_1.expect)(res.status()).toBe(200);
50
- const json = await res.json();
51
- (0, test_1.expect)(json.statusCode).toBe(200);
52
- (0, test_1.expect)(Array.isArray(json.message)).toBe(true);
53
- (0, test_1.expect)(json.message.length).toBe(0);
54
- (0, test_1.expect)(json.error).toBe("");
55
- const user = json.data?.user;
56
- (0, test_1.expect)(user, "Expected data.user to be an object.").toBeTruthy();
57
- (0, test_1.expect)(typeof user).toBe("object");
58
- const email = user?.email;
59
- (0, test_1.expect)(typeof email).toBe("string");
60
- if (email === TEST_USER_EMAIL) {
61
- (0, test_1.expect)(email).toBe(TEST_USER_EMAIL);
62
- }
63
- else {
64
- (0, test_1.expect)(email.length).toBeGreaterThan(0);
65
- }
66
- (0, test_1.expect)(typeof user?.mobile).toBe("string");
67
- (0, test_1.expect)(typeof user?.username).toBe("string");
68
- (0, test_1.expect)(typeof user?.forcePasswordChange).toBe("boolean");
69
- (0, test_1.expect)(typeof user?.id).toBe("number");
70
- const roles = user?.roles;
71
- (0, test_1.expect)(Array.isArray(roles)).toBe(true);
72
- if (Array.isArray(roles)) {
73
- (0, test_1.expect)(roles.every((role) => typeof role === "string")).toBe(true);
74
- (0, test_1.expect)(roles).toContain("Admin");
75
- }
76
- const accessToken = json.data?.accessToken;
77
- const refreshToken = json.data?.refreshToken;
78
- (0, test_1.expect)(typeof accessToken).toBe("string");
79
- (0, test_1.expect)(typeof refreshToken).toBe("string");
80
- const accessPayload = validateJwt(accessToken);
81
- const refreshPayload = validateJwt(refreshToken);
82
- (0, test_1.expect)(typeof accessPayload.exp).toBe("number");
83
- (0, test_1.expect)(typeof refreshPayload.exp).toBe("number");
84
- }
85
- finally {
86
- await api.dispose();
87
- }
88
- });
89
- (0, test_1.test)("API: authenticate fails with wrong password", async () => {
90
- const api = await test_1.request.newContext({
91
- baseURL,
92
- extraHTTPHeaders: {
93
- accept: "*/*",
94
- "content-type": "application/json",
95
- },
96
- });
97
- try {
98
- const res = await api.post("/api/iam/authenticate", {
99
- data: {
100
- email: TEST_USER_EMAIL,
101
- username: "",
102
- password: `${TEST_USER_PASSWORD}__wrong`,
103
- },
104
- });
105
- (0, test_1.expect)(res.status()).toBe(401);
106
- const json = await res.json();
107
- (0, test_1.expect)(json.statusCode).toBe(401);
108
- (0, test_1.expect)(json.statusCodeMessage).toBe("Unauthorized");
109
- (0, test_1.expect)(json.message).toBe("Invalid credentials");
110
- (0, test_1.expect)(json.error).toBe("Invalid credentials");
111
- (0, test_1.expect)(json.data?.statusCode).toBe(401);
112
- (0, test_1.expect)(json.data?.error).toBe("Unauthorized");
113
- (0, test_1.expect)(json.data?.message).toBe("Invalid credentials");
114
- }
115
- finally {
116
- await api.dispose();
117
- }
118
- });
119
- //# sourceMappingURL=authenticate.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"authenticate.spec.js","sourceRoot":"","sources":["../../tests/api/authenticate.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\nimport { getRequiredEnv } from \"../helpers/env\";\n\ntype JwtPayload = {\n exp?: number;\n [key: string]: unknown;\n};\n\nconst baseURL = process.env.API_BASE_URL ?? \"http://localhost:3000\";\nconst TEST_USER_EMAIL = getRequiredEnv(\"TEST_USER_EMAIL\");\nconst TEST_USER_PASSWORD = getRequiredEnv(\"TEST_USER_PASSWORD\");\n\nfunction base64UrlDecode(input: string): string {\n const normalized = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded =\n normalized.length % 4 === 0\n ? normalized\n : normalized.padEnd(\n normalized.length + (4 - (normalized.length % 4)),\n \"=\"\n );\n return Buffer.from(padded, \"base64\").toString(\"utf-8\");\n}\n\nfunction validateJwt(token: string): JwtPayload {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"JWT must have three dot-separated parts.\");\n }\n\n const headerJson = JSON.parse(base64UrlDecode(parts[0]));\n const payloadJson = JSON.parse(base64UrlDecode(parts[1]));\n\n if (!headerJson || typeof headerJson !== \"object\") {\n throw new Error(\"JWT header must be a JSON object.\");\n }\n\n if (!payloadJson || typeof payloadJson !== \"object\") {\n throw new Error(\"JWT payload must be a JSON object.\");\n }\n\n if (typeof payloadJson.exp !== \"number\") {\n throw new Error(\"JWT payload.exp must be a number.\");\n }\n\n return payloadJson as JwtPayload;\n}\n\ntest(\"API: authenticate succeeds with valid credentials\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: TEST_USER_PASSWORD,\n },\n });\n\n expect(res.status()).toBe(200);\n const json = await res.json();\n\n expect(json.statusCode).toBe(200);\n expect(Array.isArray(json.message)).toBe(true);\n expect(json.message.length).toBe(0);\n expect(json.error).toBe(\"\");\n\n const user = json.data?.user as Record<string, unknown> | undefined;\n expect(user, \"Expected data.user to be an object.\").toBeTruthy();\n expect(typeof user).toBe(\"object\");\n\n const email = user?.email;\n expect(typeof email).toBe(\"string\");\n if (email === TEST_USER_EMAIL) {\n expect(email).toBe(TEST_USER_EMAIL);\n } else {\n // If your API returns a fixed system email, replace this with an exact match.\n expect((email as string).length).toBeGreaterThan(0);\n }\n\n expect(typeof user?.mobile).toBe(\"string\");\n expect(typeof user?.username).toBe(\"string\");\n expect(typeof user?.forcePasswordChange).toBe(\"boolean\");\n expect(typeof user?.id).toBe(\"number\");\n\n const roles = user?.roles;\n expect(Array.isArray(roles)).toBe(true);\n if (Array.isArray(roles)) {\n expect(roles.every((role) => typeof role === \"string\")).toBe(true);\n expect(roles).toContain(\"Admin\");\n }\n\n const accessToken = json.data?.accessToken;\n const refreshToken = json.data?.refreshToken;\n expect(typeof accessToken).toBe(\"string\");\n expect(typeof refreshToken).toBe(\"string\");\n\n const accessPayload = validateJwt(accessToken as string);\n const refreshPayload = validateJwt(refreshToken as string);\n\n expect(typeof accessPayload.exp).toBe(\"number\");\n expect(typeof refreshPayload.exp).toBe(\"number\");\n } finally {\n await api.dispose();\n }\n});\n\ntest(\"API: authenticate fails with wrong password\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: `${TEST_USER_PASSWORD}__wrong`,\n },\n });\n\n expect(res.status()).toBe(401);\n const json = await res.json();\n\n expect(json.statusCode).toBe(401);\n expect(json.statusCodeMessage).toBe(\"Unauthorized\");\n expect(json.message).toBe(\"Invalid credentials\");\n expect(json.error).toBe(\"Invalid credentials\");\n expect(json.data?.statusCode).toBe(401);\n expect(json.data?.error).toBe(\"Unauthorized\");\n expect(json.data?.message).toBe(\"Invalid credentials\");\n } finally {\n await api.dispose();\n }\n});\n"]}
@@ -1,97 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const test_1 = require("@playwright/test");
4
- const auth_1 = require("../helpers/auth");
5
- const baseURL = process.env.API_BASE_URL ?? "http://localhost:3000";
6
- async function getCityByName(name) {
7
- const api = await test_1.request.newContext({
8
- baseURL,
9
- extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
10
- });
11
- try {
12
- const res = await api.get(`/api/city-master?filters[name][$eq]=${encodeURIComponent(name)}&limit=1&offset=0`);
13
- (0, test_1.expect)(res.status()).toBe(200);
14
- const json = await res.json();
15
- const record = json?.data?.records?.[0];
16
- (0, test_1.expect)(record, `Expected city '${name}' in testData`).toBeTruthy();
17
- (0, test_1.expect)(record.id, "Expected record to have id").toBeTruthy();
18
- return record;
19
- }
20
- finally {
21
- await api.dispose();
22
- }
23
- }
24
- test_1.test.describe("CRUDService.findOne (cityMaster)", () => {
25
- (0, test_1.test)("returns a city by id", async () => {
26
- const city = await getCityByName("Mumbai");
27
- const api = await test_1.request.newContext({
28
- baseURL,
29
- extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
30
- });
31
- try {
32
- const res = await api.get(`/api/city-master/${city.id}`);
33
- (0, test_1.expect)(res.status()).toBe(200);
34
- const json = await res.json();
35
- (0, test_1.expect)(json?.data?.id).toBe(city.id);
36
- (0, test_1.expect)(json?.data?.name).toBe("Mumbai");
37
- (0, test_1.expect)(json?.data?.description).toBe("Mumbai city");
38
- }
39
- finally {
40
- await api.dispose();
41
- }
42
- });
43
- (0, test_1.test)("supports fields[] selection", async () => {
44
- const city = await getCityByName("Pune");
45
- const api = await test_1.request.newContext({
46
- baseURL,
47
- extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
48
- });
49
- try {
50
- const res = await api.get(`/api/city-master/${city.id}?fields[]=id&fields[]=name`);
51
- (0, test_1.expect)(res.status()).toBe(200);
52
- const json = await res.json();
53
- (0, test_1.expect)(json?.data?.id).toBe(city.id);
54
- (0, test_1.expect)(json?.data?.name).toBe("Pune");
55
- (0, test_1.expect)(json?.data?.description).toBeUndefined();
56
- (0, test_1.expect)(json?.data?.state).toBeUndefined();
57
- }
58
- finally {
59
- await api.dispose();
60
- }
61
- });
62
- (0, test_1.test)("supports populate=state", async () => {
63
- const city = await getCityByName("Bengaluru");
64
- const api = await test_1.request.newContext({
65
- baseURL,
66
- extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
67
- });
68
- try {
69
- const res = await api.get(`/api/city-master/${city.id}?populate=state`);
70
- (0, test_1.expect)(res.status()).toBe(200);
71
- const json = await res.json();
72
- (0, test_1.expect)(json?.data?.name).toBe("Bengaluru");
73
- (0, test_1.expect)(json?.data?.state).toBeTruthy();
74
- (0, test_1.expect)(json?.data?.state?.name).toBe("Karnataka");
75
- }
76
- finally {
77
- await api.dispose();
78
- }
79
- });
80
- (0, test_1.test)("returns 404 for missing id", async () => {
81
- const api = await test_1.request.newContext({
82
- baseURL,
83
- extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
84
- });
85
- try {
86
- const res = await api.get(`/api/city-master/99999999`);
87
- (0, test_1.expect)(res.status()).toBe(404);
88
- const json = await res.json();
89
- (0, test_1.expect)(String(json?.message)).toContain("cityMaster");
90
- (0, test_1.expect)(String(json?.message)).toContain("not found");
91
- }
92
- finally {
93
- await api.dispose();
94
- }
95
- });
96
- });
97
- //# sourceMappingURL=crud-service.findOne.cityMaster.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"crud-service.findOne.cityMaster.spec.js","sourceRoot":"","sources":["../../tests/api/crud-service.findOne.cityMaster.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\nimport { getAuthHeaders } from \"../helpers/auth\";\n\nconst baseURL = process.env.API_BASE_URL ?? \"http://localhost:3000\";\n\nasync function getCityByName(name: string) {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(\n `/api/city-master?filters[name][$eq]=${encodeURIComponent(name)}&limit=1&offset=0`\n );\n expect(res.status()).toBe(200);\n const json = await res.json();\n\n const record = json?.data?.records?.[0];\n expect(record, `Expected city '${name}' in testData`).toBeTruthy();\n expect(record.id, \"Expected record to have id\").toBeTruthy();\n\n return record;\n } finally {\n await api.dispose();\n }\n}\n\ntest.describe(\"CRUDService.findOne (cityMaster)\", () => {\n test(\"returns a city by id\", async () => {\n const city = await getCityByName(\"Mumbai\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/${city.id}`);\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.id).toBe(city.id);\n expect(json?.data?.name).toBe(\"Mumbai\");\n expect(json?.data?.description).toBe(\"Mumbai city\");\n } finally {\n await api.dispose();\n }\n });\n\n test(\"supports fields[] selection\", async () => {\n const city = await getCityByName(\"Pune\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(\n `/api/city-master/${city.id}?fields[]=id&fields[]=name`\n );\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.id).toBe(city.id);\n expect(json?.data?.name).toBe(\"Pune\");\n expect(json?.data?.description).toBeUndefined();\n expect(json?.data?.state).toBeUndefined();\n } finally {\n await api.dispose();\n }\n });\n\n test(\"supports populate=state\", async () => {\n const city = await getCityByName(\"Bengaluru\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/${city.id}?populate=state`);\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.name).toBe(\"Bengaluru\");\n expect(json?.data?.state).toBeTruthy();\n expect(json?.data?.state?.name).toBe(\"Karnataka\");\n } finally {\n await api.dispose();\n }\n });\n\n test(\"returns 404 for missing id\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/99999999`);\n expect(res.status()).toBe(404);\n\n const json = await res.json();\n expect(String(json?.message)).toContain(\"cityMaster\");\n expect(String(json?.message)).toContain(\"not found\");\n } finally {\n await api.dispose();\n }\n });\n});\n"]}
@@ -1,21 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const test_1 = require("@playwright/test");
4
- (0, test_1.test)("GET /api/ping returns pong", async () => {
5
- const baseURL = process.env.BASE_URL || "http://localhost:3000";
6
- if (!baseURL) {
7
- throw new Error("baseURL is not configured. Set API_BASE_URL or use the default.");
8
- }
9
- const api = await test_1.request.newContext({ baseURL });
10
- const res = await api.get("/api/ping");
11
- (0, test_1.expect)(res.status()).toBe(200);
12
- const body = await res.json();
13
- (0, test_1.expect)(body).toEqual({
14
- statusCode: 200,
15
- message: [],
16
- error: "",
17
- data: { pong: "v1.0.2" },
18
- });
19
- await api.dispose();
20
- });
21
- //# sourceMappingURL=ping.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ping.spec.js","sourceRoot":"","sources":["../../tests/api/ping.spec.ts"],"names":[],"mappings":";;AAAA,2CAAyD;AAEzD,IAAA,WAAI,EAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;IAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,uBAAuB,CAAC;IAChE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,cAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IAElD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAEvC,IAAA,aAAM,EAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE/B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAA,aAAM,EAAC,IAAI,CAAC,CAAC,OAAO,CAAC;QACnB,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,EAAE;QACT,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;KACzB,CAAC,CAAC;IAEH,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;AACtB,CAAC,CAAC,CAAC","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\n\ntest(\"GET /api/ping returns pong\", async () => {\n const baseURL = process.env.BASE_URL || \"http://localhost:3000\";\n if (!baseURL) {\n throw new Error(\"baseURL is not configured. Set API_BASE_URL or use the default.\");\n }\n const api = await request.newContext({ baseURL });\n\n const res = await api.get(\"/api/ping\");\n\n expect(res.status()).toBe(200);\n\n const body = await res.json();\n expect(body).toEqual({\n statusCode: 200,\n message: [],\n error: \"\",\n data: { pong: \"v1.0.2\" },\n });\n\n await api.dispose();\n});\n"]}