fix(coreflow): Fix Coreflow identity lookup and response shape; improve API client tests and bump dependencies

This commit is contained in:
2025-08-18 21:11:28 +00:00
parent 23c9e3f678
commit af7fcf6c2e
7 changed files with 456 additions and 217 deletions

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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 () => {
const identity = await testClient.getIdentityByToken('test'); console.log('🔵 Test: Getting identity by token...');
expect(identity).toBeTruthy(); console.log(` - Using token: 'test'`);
console.log(identity); console.log(` - API URL: http://${helpers.testCloudlyConfig.publicUrl}:${helpers.testCloudlyConfig.publicPort}`);
try {
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();
} 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 () => {
image = await testClient.image.createImage({ console.log('🔵 Test: Creating and uploading image...');
name: 'test', console.log(` - Image name: 'test'`);
description: 'test' console.log(` - Image description: 'test'`);
});
console.log('created image: ', image); try {
expect(image).toBeInstanceOf(Image); image = await testClient.image.createImage({
name: 'test',
description: 'test'
});
console.log('✅ Image created successfully:');
console.log(` - Image ID: ${image?.id}`);
console.log(` - Image data:`, 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 () => {
const imageStream = await helpers.getAlpineImageReadableStream(); console.log('🔵 Test: Uploading image version...');
console.log(` - Version: 'v1.0.0'`);
await image.pushImageVersion('v1.0.0', imageStream); console.log(` - Image exists: ${!!image}`);
console.log(` - Image ID: ${image?.id}`);
try {
const imageStream = await helpers.getAlpineImageReadableStream();
console.log(' - Image stream obtained successfully');
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) => {

View File

@@ -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.'
} }

View File

@@ -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(),
}; };
}, }
), )
); );
} }
} }

View File

@@ -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.'
} }