231 lines
8.4 KiB
JavaScript
231 lines
8.4 KiB
JavaScript
describe('Task Tracker 2key', function() {
|
|
this.timeout(120000); // 2 minutes for complex integration tests
|
|
|
|
it('should execute test successfully', async function() {
|
|
const { expect } = require('chai');
|
|
'use strict';
|
|
|
|
/**
|
|
* Test TaskTracker 2-Key Design
|
|
*
|
|
* Demonstrates:
|
|
* - taskId generation (stable across retries)
|
|
* - executionId generation (unique per attempt)
|
|
* - Deduplication checking
|
|
* - Retry chain tracing
|
|
* - No separate correlationId needed!
|
|
*/
|
|
|
|
const path = require('path');
|
|
|
|
// === Environment Setup ===
|
|
const args = process.argv.slice(2);
|
|
let envFile = './environment.env';
|
|
for (let i = 0; i < args.length; i++) {
|
|
if (args[i] === '--env' && args[i + 1]) {
|
|
envFile = args[i + 1];
|
|
i++;
|
|
}
|
|
}
|
|
|
|
const envPath = path.resolve(process.cwd(), envFile);
|
|
require('dotenv').config({ path: envPath });
|
|
|
|
// === Initialize ===
|
|
const mongoose = require('mongoose');
|
|
const { DBConnection } = require('../../helpers/db/connect');
|
|
const { generateTaskId, generateExecutionId, isValidTaskId, isValidExecutionId } = require('../../services/task_id_generator');
|
|
const TaskTracker = require('../../model/task_tracker');
|
|
const { TaskTrackerStatus } = require('../../model/task_tracker');
|
|
|
|
async function test() {
|
|
try {
|
|
// Connect to database
|
|
console.log('\n=== Connecting to MongoDB ===');
|
|
const dbConn = new DBConnection('TaskTrackerTest');
|
|
await dbConn.connect({
|
|
debugMode: false,
|
|
exitOnError: false,
|
|
setupEventListeners: false,
|
|
setupExitHandlers: false
|
|
});
|
|
console.log('✓ Connected\n');
|
|
|
|
// === Test 1: Generate taskId and executionId ===
|
|
console.log('=== Test 1: Generate IDs ===');
|
|
const message = {
|
|
partnerCode: 'SATLOC',
|
|
aircraftId: '695d',
|
|
logId: '02220710',
|
|
customerId: '507f1f77bcf86cd799439011',
|
|
logFileName: '02220710.log.txt'
|
|
};
|
|
|
|
const taskId = generateTaskId('dev_partner_tasks', message);
|
|
const executionId1 = generateExecutionId();
|
|
const executionId2 = generateExecutionId();
|
|
|
|
console.log('taskId:', taskId);
|
|
console.log(' Valid:', isValidTaskId(taskId));
|
|
console.log(' Format: {queueType}:{partnerCode}:{aircraftId}:{logId}');
|
|
console.log('\nexecutionId (attempt 1):', executionId1);
|
|
console.log(' Valid:', isValidExecutionId(executionId1));
|
|
console.log(' Format: UUID v4');
|
|
console.log('\nexecutionId (attempt 2):', executionId2);
|
|
console.log(' Valid:', isValidExecutionId(executionId2));
|
|
console.log(' Different from attempt 1:', executionId1 !== executionId2);
|
|
|
|
// === Test 2: Deduplication Check ===
|
|
console.log('\n=== Test 2: Deduplication Check ===');
|
|
|
|
// Clean up previous test data
|
|
await TaskTracker.deleteMany({ taskId });
|
|
|
|
// First enqueue
|
|
const tracker1 = await TaskTracker.create({
|
|
taskId,
|
|
executionId: executionId1,
|
|
queueName: 'dev_partner_tasks',
|
|
status: TaskTrackerStatus.QUEUED,
|
|
metadata: message
|
|
});
|
|
console.log('✓ Created tracker 1:', tracker1._id);
|
|
|
|
// Check for duplicate (should find existing)
|
|
const recentTask = await TaskTracker.findOne({
|
|
taskId,
|
|
status: { $in: [TaskTrackerStatus.QUEUED, TaskTrackerStatus.PROCESSING] },
|
|
enqueuedAt: { $gt: new Date(Date.now() - 5 * 60000) }
|
|
});
|
|
|
|
if (recentTask) {
|
|
console.log('✓ Deduplication works: Found existing task, would skip enqueue');
|
|
console.log(' Existing executionId:', recentTask.executionId);
|
|
console.log(' Status:', recentTask.status);
|
|
} else {
|
|
console.log('✗ Deduplication failed: Should have found existing task');
|
|
}
|
|
|
|
// === Test 3: Idempotency (Atomic Claim) ===
|
|
console.log('\n=== Test 3: Idempotency (Atomic Claim) ===');
|
|
|
|
// Worker 1 claims task
|
|
const claimed1 = await TaskTracker.findOneAndUpdate(
|
|
{
|
|
taskId,
|
|
executionId: executionId1,
|
|
status: { $in: [TaskTrackerStatus.QUEUED, TaskTrackerStatus.FAILED] }
|
|
},
|
|
{
|
|
$set: {
|
|
status: TaskTrackerStatus.PROCESSING,
|
|
processingStartedAt: new Date()
|
|
}
|
|
},
|
|
{ new: true }
|
|
);
|
|
|
|
if (claimed1) {
|
|
console.log('✓ Worker 1 claimed task successfully');
|
|
console.log(' Status changed to:', claimed1.status);
|
|
}
|
|
|
|
// Worker 2 tries to claim same task (should fail)
|
|
const claimed2 = await TaskTracker.findOneAndUpdate(
|
|
{
|
|
taskId,
|
|
executionId: executionId1,
|
|
status: { $in: [TaskTrackerStatus.QUEUED, TaskTrackerStatus.FAILED] }
|
|
},
|
|
{
|
|
$set: {
|
|
status: TaskTrackerStatus.PROCESSING,
|
|
processingStartedAt: new Date()
|
|
}
|
|
},
|
|
{ new: true }
|
|
);
|
|
|
|
if (!claimed2) {
|
|
console.log('✓ Worker 2 cannot claim: Task already claimed (idempotency works!)');
|
|
} else {
|
|
console.log('✗ Idempotency failed: Worker 2 should not have claimed task');
|
|
}
|
|
|
|
// === Test 4: Retry Chain (No Separate CorrelationId Needed!) ===
|
|
console.log('\n=== Test 4: Retry Chain Tracing ===');
|
|
|
|
// Complete first attempt
|
|
await TaskTracker.updateOne(
|
|
{ executionId: executionId1 },
|
|
{ $set: { status: TaskTrackerStatus.FAILED, errorMessage: 'Network timeout', retryCount: 1 } }
|
|
);
|
|
console.log('✓ Marked first attempt as FAILED');
|
|
|
|
// Create retry (same taskId, new executionId)
|
|
const tracker2 = await TaskTracker.create({
|
|
taskId, // Same taskId!
|
|
executionId: executionId2, // New executionId
|
|
queueName: 'dev_partner_tasks',
|
|
status: TaskTrackerStatus.QUEUED,
|
|
retryCount: 1,
|
|
metadata: message
|
|
});
|
|
console.log('✓ Created retry tracker:', tracker2._id);
|
|
|
|
// Complete retry
|
|
await TaskTracker.updateOne(
|
|
{ executionId: executionId2 },
|
|
{ $set: { status: TaskTrackerStatus.COMPLETED, completedAt: new Date() } }
|
|
);
|
|
console.log('✓ Marked retry as COMPLETED');
|
|
|
|
// Find complete retry chain (using taskId - no correlationId needed!)
|
|
const retryChain = await TaskTracker.find({ taskId })
|
|
.sort({ createdAt: 1 })
|
|
.lean();
|
|
|
|
console.log('\n✓ Retry chain found via taskId (no correlationId needed!):');
|
|
retryChain.forEach((execution, index) => {
|
|
console.log(` Attempt ${index + 1}:`);
|
|
console.log(` executionId: ${execution.executionId.substring(0, 8)}...`);
|
|
console.log(` status: ${execution.status}`);
|
|
console.log(` error: ${execution.errorMessage || 'N/A'}`);
|
|
console.log(` created: ${execution.createdAt.toISOString()}`);
|
|
});
|
|
|
|
// === Test 5: Query Performance ===
|
|
console.log('\n=== Test 5: Benefits of 2-Key Design ===');
|
|
console.log('✓ Deduplication: Query by taskId only');
|
|
console.log('✓ Idempotency: Query by taskId + executionId');
|
|
console.log('✓ Tracing: Query by taskId returns complete retry chain');
|
|
console.log('✓ Simpler: 2 keys instead of 3 (taskId + idempotencyKey + correlationId)');
|
|
console.log('✓ Fewer indexes: 6 indexes vs 9+ in old design');
|
|
console.log('✓ Better performance: Fewer fields to compare in queries');
|
|
|
|
// === Cleanup ===
|
|
console.log('\n=== Cleanup ===');
|
|
const deleted = await TaskTracker.deleteMany({ taskId });
|
|
console.log(`✓ Deleted ${deleted.deletedCount} test records`);
|
|
|
|
console.log('\n=== All Tests Passed! ===\n');
|
|
|
|
} catch (error) {
|
|
console.error('Test failed:', error);
|
|
process.exit(1);
|
|
} finally {
|
|
await mongoose.connection.close();
|
|
}
|
|
}
|
|
|
|
// Run tests
|
|
test().then(() => {
|
|
console.log('Exit Code: 0');
|
|
process.exit(0);
|
|
}).catch(err => {
|
|
console.error('Fatal error:', err);
|
|
process.exit(1);
|
|
});
|
|
|
|
});
|
|
}); |