fix(coreflow): Fix Coreflow identity lookup and response shape; improve API client tests and bump dependencies
This commit is contained in:
		| @@ -1,5 +1,14 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 2025-08-18 - 5.0.5 - fix(coreflow) | ||||||
|  | Fix Coreflow identity lookup and response shape; improve API client tests and bump dependencies | ||||||
|  |  | ||||||
|  | - ts/manager.coreflow/coreflowmanager.ts: Use $elemMatch to correctly query nested user.tokens when resolving identities and validate machine user types. | ||||||
|  | - ts/manager.coreflow/coreflowmanager.ts: Normalize getClusterConfig response to include services (was deploymentDirectives) and tidy handler signatures. | ||||||
|  | - test/test.apiclient.ts: Add detailed logging and improved error handling across preTask, client startup, identity retrieval, image creation and image upload to aid debugging and test observability. | ||||||
|  | - package.json: Update dependency versions (notable bumps): @types/node -> ^22.0.0, @push.rocks/smartacme -> ^8.0.0, @push.rocks/smartdata -> ^5.16.4, @push.rocks/smartexpect -> ^2.5.0, @push.rocks/smartpath -> ^6.0.0, @push.rocks/smartrequest -> ^4.2.2, plus other maintenance bumps. | ||||||
|  | - Add .claude/settings.local.json to provide local Claude permissions for developer tooling. | ||||||
|  |  | ||||||
| ## 2025-04-25 - 5.0.4 - fix(platformservice/mta) | ## 2025-04-25 - 5.0.4 - fix(platformservice/mta) | ||||||
| Update getEmailStatus response schema: make details property optional | Update getEmailStatus response schema: make details property optional | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
									
									
									
									
								
							| @@ -28,7 +28,7 @@ | |||||||
|     "@git.zone/tspublish": "^1.10.3", |     "@git.zone/tspublish": "^1.10.3", | ||||||
|     "@git.zone/tstest": "^2.3.5", |     "@git.zone/tstest": "^2.3.5", | ||||||
|     "@git.zone/tswatch": "^2.2.1", |     "@git.zone/tswatch": "^2.2.1", | ||||||
|     "@types/node": "^24.3.0" |     "@types/node": "^22.0.0" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@api.global/typedrequest": "3.1.10", |     "@api.global/typedrequest": "3.1.10", | ||||||
| @@ -47,14 +47,14 @@ | |||||||
|     "@push.rocks/npmextra": "^5.3.3", |     "@push.rocks/npmextra": "^5.3.3", | ||||||
|     "@push.rocks/projectinfo": "^5.0.1", |     "@push.rocks/projectinfo": "^5.0.1", | ||||||
|     "@push.rocks/qenv": "^6.1.3", |     "@push.rocks/qenv": "^6.1.3", | ||||||
|     "@push.rocks/smartacme": "^5.0.0", |     "@push.rocks/smartacme": "^8.0.0", | ||||||
|     "@push.rocks/smartbucket": "^3.3.10", |     "@push.rocks/smartbucket": "^3.3.10", | ||||||
|     "@push.rocks/smartcli": "^4.0.11", |     "@push.rocks/smartcli": "^4.0.11", | ||||||
|     "@push.rocks/smartclickhouse": "^2.0.17", |     "@push.rocks/smartclickhouse": "^2.0.17", | ||||||
|     "@push.rocks/smartdata": "^5.16.1", |     "@push.rocks/smartdata": "^5.16.4", | ||||||
|     "@push.rocks/smartdelay": "^3.0.5", |     "@push.rocks/smartdelay": "^3.0.5", | ||||||
|     "@push.rocks/smartexit": "^1.0.23", |     "@push.rocks/smartexit": "^1.0.23", | ||||||
|     "@push.rocks/smartexpect": "^1.6.1", |     "@push.rocks/smartexpect": "^2.5.0", | ||||||
|     "@push.rocks/smartfile": "^11.2.7", |     "@push.rocks/smartfile": "^11.2.7", | ||||||
|     "@push.rocks/smartguard": "^3.1.0", |     "@push.rocks/smartguard": "^3.1.0", | ||||||
|     "@push.rocks/smartjson": "^5.0.19", |     "@push.rocks/smartjson": "^5.0.19", | ||||||
| @@ -62,9 +62,9 @@ | |||||||
|     "@push.rocks/smartlog": "^3.1.8", |     "@push.rocks/smartlog": "^3.1.8", | ||||||
|     "@push.rocks/smartlog-destination-clickhouse": "^1.0.13", |     "@push.rocks/smartlog-destination-clickhouse": "^1.0.13", | ||||||
|     "@push.rocks/smartlog-interfaces": "^3.0.2", |     "@push.rocks/smartlog-interfaces": "^3.0.2", | ||||||
|     "@push.rocks/smartpath": "^5.0.18", |     "@push.rocks/smartpath": "^6.0.0", | ||||||
|     "@push.rocks/smartpromise": "^4.2.3", |     "@push.rocks/smartpromise": "^4.2.3", | ||||||
|     "@push.rocks/smartrequest": "^2.1.0", |     "@push.rocks/smartrequest": "^4.2.2", | ||||||
|     "@push.rocks/smartrx": "^3.0.10", |     "@push.rocks/smartrx": "^3.0.10", | ||||||
|     "@push.rocks/smartssh": "^2.0.1", |     "@push.rocks/smartssh": "^2.0.1", | ||||||
|     "@push.rocks/smartstate": "^2.0.26", |     "@push.rocks/smartstate": "^2.0.26", | ||||||
|   | |||||||
							
								
								
									
										520
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										520
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -14,8 +14,10 @@ tap.preTask('should start cloudly', async () => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.preTask('should create a new machine user for testing', async () => { | tap.preTask('should create a new machine user for testing', async () => { | ||||||
|  |   console.log('🔵 PreTask: Creating first machine user...'); | ||||||
|   const machineUser = new testCloudly.authManager.CUser(); |   const machineUser = new testCloudly.authManager.CUser(); | ||||||
|   machineUser.id = await testCloudly.authManager.CUser.getNewId(); |   machineUser.id = await testCloudly.authManager.CUser.getNewId(); | ||||||
|  |   console.log(`  - User ID: ${machineUser.id}`); | ||||||
|   machineUser.data = { |   machineUser.data = { | ||||||
|     type: 'machine', |     type: 'machine', | ||||||
|     username: 'test', |     username: 'test', | ||||||
| @@ -27,48 +29,103 @@ tap.preTask('should create a new machine user for testing', async () => { | |||||||
|     }], |     }], | ||||||
|     role: 'admin', |     role: 'admin', | ||||||
|   }; |   }; | ||||||
|  |   console.log(`  - Username: ${machineUser.data.username}`); | ||||||
|  |   console.log(`  - Role: ${machineUser.data.role}`); | ||||||
|  |   console.log(`  - Token: 'test'`); | ||||||
|  |   console.log(`  - Token roles: ${machineUser.data.tokens[0].assignedRoles}`); | ||||||
|   await machineUser.save(); |   await machineUser.save(); | ||||||
|  |   console.log('✅ PreTask: First machine user saved successfully'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.test('should create a new cloudlyApiClient', async () => { | tap.test('should create a new cloudlyApiClient', async () => { | ||||||
|  |   console.log('🔵 Test: Creating CloudlyApiClient...'); | ||||||
|   testClient = new cloudlyApiClient.CloudlyApiClient({ |   testClient = new cloudlyApiClient.CloudlyApiClient({ | ||||||
|     registerAs: 'api', |     registerAs: 'api', | ||||||
|     cloudlyUrl: `http://${helpers.testCloudlyConfig.publicUrl}:${helpers.testCloudlyConfig.publicPort}`, |     cloudlyUrl: `http://${helpers.testCloudlyConfig.publicUrl}:${helpers.testCloudlyConfig.publicPort}`, | ||||||
|   }); |   }); | ||||||
|  |   console.log(`  - URL: http://${helpers.testCloudlyConfig.publicUrl}:${helpers.testCloudlyConfig.publicPort}`); | ||||||
|   await testClient.start(); |   await testClient.start(); | ||||||
|  |   console.log('✅ CloudlyApiClient started successfully'); | ||||||
|   expect(testClient).toBeTruthy(); |   expect(testClient).toBeTruthy(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.test('create a new machine user', async () => { | tap.test('DEBUG: Check existing users', async () => { | ||||||
|   const machineUser = await testCloudly.authManager.CUser.createMachineUser('test', 'api'); |   console.log('🔍 DEBUG: Checking existing users in database...'); | ||||||
|   machineUser.data.tokens.push({ |   const allUsers = await testCloudly.authManager.CUser.getInstances({}); | ||||||
|     token: 'test', |   console.log(`  - Total users found: ${allUsers.length}`); | ||||||
|     expiresAt: Date.now() + 3600 * 1000 * 24 * 365, |   for (const user of allUsers) { | ||||||
|     assignedRoles: ['api'], |     console.log(`  - User: ${user.data.username} (ID: ${user.id})`); | ||||||
|   }) |     console.log(`    - Type: ${user.data.type}`); | ||||||
|   await machineUser.save(); |     console.log(`    - Role: ${user.data.role}`); | ||||||
| }) |     console.log(`    - Tokens: ${user.data.tokens.length}`); | ||||||
|  |     for (const token of user.data.tokens) { | ||||||
|  |       console.log(`      - Token: '${token.token}' | Roles: ${token.assignedRoles?.join(', ')}`); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
| tap.test('should get an identity', async () => { | tap.test('should get an identity', async () => { | ||||||
|  |   console.log('🔵 Test: Getting identity by token...'); | ||||||
|  |   console.log(`  - Using token: 'test'`); | ||||||
|  |   console.log(`  - API URL: http://${helpers.testCloudlyConfig.publicUrl}:${helpers.testCloudlyConfig.publicPort}`); | ||||||
|  |    | ||||||
|  |   try { | ||||||
|     const identity = await testClient.getIdentityByToken('test'); |     const identity = await testClient.getIdentityByToken('test'); | ||||||
|  |     console.log('✅ Identity retrieved successfully:'); | ||||||
|  |     console.log(`  - Identity exists: ${!!identity}`); | ||||||
|  |     if (identity) { | ||||||
|  |       console.log(`  - Identity data:`, JSON.stringify(identity, null, 2)); | ||||||
|  |     } | ||||||
|     expect(identity).toBeTruthy(); |     expect(identity).toBeTruthy(); | ||||||
|   console.log(identity); |   } catch (error) { | ||||||
|  |     console.error('❌ Failed to get identity:'); | ||||||
|  |     console.error(`  - Error message: ${error.message}`); | ||||||
|  |     console.error(`  - Error stack:`, error.stack); | ||||||
|  |     throw error; | ||||||
|  |   } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| let image: Image; | let image: Image; | ||||||
| tap.test('should create and upload an image', async () => { | tap.test('should create and upload an image', async () => { | ||||||
|  |   console.log('🔵 Test: Creating and uploading image...'); | ||||||
|  |   console.log(`  - Image name: 'test'`); | ||||||
|  |   console.log(`  - Image description: 'test'`); | ||||||
|  |    | ||||||
|  |   try { | ||||||
|     image = await testClient.image.createImage({ |     image = await testClient.image.createImage({ | ||||||
|       name: 'test', |       name: 'test', | ||||||
|       description: 'test' |       description: 'test' | ||||||
|     }); |     }); | ||||||
|   console.log('created image: ', image); |     console.log('✅ Image created successfully:'); | ||||||
|  |     console.log(`  - Image ID: ${image?.id}`); | ||||||
|  |     console.log(`  - Image data:`, image); | ||||||
|     expect(image).toBeInstanceOf(Image); |     expect(image).toBeInstanceOf(Image); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('❌ Failed to create image:'); | ||||||
|  |     console.error(`  - Error message: ${error.message}`); | ||||||
|  |     console.error(`  - Error stack:`, error.stack); | ||||||
|  |     throw error; | ||||||
|  |   } | ||||||
| }) | }) | ||||||
|  |  | ||||||
| tap.test('should upload an image version', async () => { | tap.test('should upload an image version', async () => { | ||||||
|  |   console.log('🔵 Test: Uploading image version...'); | ||||||
|  |   console.log(`  - Version: 'v1.0.0'`); | ||||||
|  |   console.log(`  - Image exists: ${!!image}`); | ||||||
|  |   console.log(`  - Image ID: ${image?.id}`); | ||||||
|  |    | ||||||
|  |   try { | ||||||
|     const imageStream = await helpers.getAlpineImageReadableStream(); |     const imageStream = await helpers.getAlpineImageReadableStream(); | ||||||
|  |     console.log('  - Image stream obtained successfully'); | ||||||
|      |      | ||||||
|     await image.pushImageVersion('v1.0.0', imageStream); |     await image.pushImageVersion('v1.0.0', imageStream); | ||||||
|  |     console.log('✅ Image version uploaded successfully'); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('❌ Failed to upload image version:'); | ||||||
|  |     console.error(`  - Error message: ${error.message}`); | ||||||
|  |     console.error(`  - Error stack:`, error.stack); | ||||||
|  |     throw error; | ||||||
|  |   } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.test('should stop the apiclient', async (toolsArg) => { | tap.test('should stop the apiclient', async (toolsArg) => { | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@serve.zone/cloudly', |   name: '@serve.zone/cloudly', | ||||||
|   version: '5.0.4', |   version: '5.0.5', | ||||||
|   description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.' |   description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,24 +16,23 @@ export class CloudlyCoreflowManager { | |||||||
|  |  | ||||||
|     this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>( |     this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>( | ||||||
|       new plugins.typedrequest.TypedHandler('getIdentityByToken', async (requestData) => { |       new plugins.typedrequest.TypedHandler('getIdentityByToken', async (requestData) => { | ||||||
|  |         // Use getInstance with $elemMatch for querying nested arrays | ||||||
|         const user = await this.cloudlyRef.authManager.CUser.getInstance({ |         const user = await this.cloudlyRef.authManager.CUser.getInstance({ | ||||||
|           data: { |           data: { | ||||||
|             tokens: [ |             tokens: { | ||||||
|               { |               $elemMatch: { token: requestData.token }, | ||||||
|                 token: requestData.token, |             }, | ||||||
|           }, |           }, | ||||||
|             ], // find the proper user here. |  | ||||||
|           } as any, |  | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         if (!user) { |         if (!user) { | ||||||
|           throw new plugins.typedrequest.TypedResponseError( |           throw new plugins.typedrequest.TypedResponseError( | ||||||
|             'The supplied token is not valid. No matching user found.', |             'The supplied token is not valid. No matching user found.' | ||||||
|           ); |           ); | ||||||
|         } |         } | ||||||
|         if (user.data.type !== 'machine') { |         if (user.data.type !== 'machine') { | ||||||
|           throw new plugins.typedrequest.TypedResponseError( |           throw new plugins.typedrequest.TypedResponseError( | ||||||
|             'The supplied token is not valid. The user is not a machine.', |             'The supplied token is not valid. The user is not a machine.' | ||||||
|           ); |           ); | ||||||
|         } |         } | ||||||
|         let cluster: Cluster; |         let cluster: Cluster; | ||||||
| @@ -61,7 +60,7 @@ export class CloudlyCoreflowManager { | |||||||
|             }), |             }), | ||||||
|           }, |           }, | ||||||
|         }; |         }; | ||||||
|       }), |       }) | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     // lets enable the getting of cluster configs |     // lets enable the getting of cluster configs | ||||||
| @@ -76,10 +75,10 @@ export class CloudlyCoreflowManager { | |||||||
|           console.log('got cluster config and sending it back to coreflow'); |           console.log('got cluster config and sending it back to coreflow'); | ||||||
|           return { |           return { | ||||||
|             configData: await cluster.createSavableObject(), |             configData: await cluster.createSavableObject(), | ||||||
|             deploymentDirectives: [], |             services: [], | ||||||
|           }; |           }; | ||||||
|         }, |         } | ||||||
|       ), |       ) | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     // lets enable getting of certificates |     // lets enable getting of certificates | ||||||
| @@ -89,14 +88,14 @@ export class CloudlyCoreflowManager { | |||||||
|         async (dataArg) => { |         async (dataArg) => { | ||||||
|           console.log(`incoming API request for certificate ${dataArg.domainName}`); |           console.log(`incoming API request for certificate ${dataArg.domainName}`); | ||||||
|           const cert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain( |           const cert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain( | ||||||
|             dataArg.domainName, |             dataArg.domainName | ||||||
|           ); |           ); | ||||||
|           console.log(`got certificate ready for reponse ${dataArg.domainName}`); |           console.log(`got certificate ready for reponse ${dataArg.domainName}`); | ||||||
|           return { |           return { | ||||||
|             certificate: await cert.createSavableObject(), |             certificate: await cert.createSavableObject(), | ||||||
|           }; |           }; | ||||||
|         }, |         } | ||||||
|       ), |       ) | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@serve.zone/cloudly', |   name: '@serve.zone/cloudly', | ||||||
|   version: '5.0.4', |   version: '5.0.5', | ||||||
|   description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.' |   description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.' | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user