queen-mq 0.1.0 → 0.1.1

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.
@@ -0,0 +1,174 @@
1
+ # Test Migration Issues and Fixes
2
+
3
+ ## Summary
4
+
5
+ When migrating tests from the old `queenClient` interface to the new minimalist `Queen` client interface, we discovered client bugs that needed fixing. The system is transactional - ACKs immediately release partition locks when awaited.
6
+
7
+ ## Client Bugs Fixed
8
+
9
+ ### 1. Null Payload Handling in `push()`
10
+ **Issue**: `typeof null === 'object'` is `true` in JavaScript, causing the code to try to access properties on `null` payloads.
11
+
12
+ **Location**: `src/client/client.js` line 153
13
+
14
+ **Fix**:
15
+ ```javascript
16
+ // Before
17
+ const isMessageObject = typeof item === 'object' &&
18
+ (item._payload || item._transactionId || item._traceId);
19
+
20
+ // After
21
+ const isMessageObject = typeof item === 'object' && item !== null &&
22
+ (item._payload || item._transactionId || item._traceId);
23
+ ```
24
+
25
+ **Why**: Tests correctly validate edge cases including null payloads. The client must handle them gracefully.
26
+
27
+ ### 2. Null Handling in `ack()`
28
+ **Issue**: Same null-checking issue when extracting transaction ID from message object.
29
+
30
+ **Location**: `src/client/client.js` line 330
31
+
32
+ **Fix**:
33
+ ```javascript
34
+ // Before
35
+ const transactionId = typeof message === 'object' ?
36
+ (message.transactionId || message.id) :
37
+ message;
38
+
39
+ // After
40
+ const transactionId = typeof message === 'object' && message !== null ?
41
+ (message.transactionId || message.id) :
42
+ message;
43
+ ```
44
+
45
+ ### 3. Consumer Group Parsing for Namespace/Task Addresses
46
+ **Issue**: The `#parseAddress()` method didn't extract consumer groups from namespace/task patterns like `namespace:billing@group-a`.
47
+
48
+ **Location**: `src/client/client.js` line 77-101
49
+
50
+ **Fix**: Extract `@group` portion before parsing namespace/task segments:
51
+ ```javascript
52
+ #parseAddress(address) {
53
+ if (address.includes('namespace:') || address.includes('task:')) {
54
+ const parts = {};
55
+
56
+ // Extract consumer group if present (after @)
57
+ let workingAddress = address;
58
+ const atIndex = address.lastIndexOf('@');
59
+ if (atIndex > 0) {
60
+ parts.consumerGroup = address.substring(atIndex + 1);
61
+ workingAddress = address.substring(0, atIndex);
62
+ }
63
+
64
+ const segments = workingAddress.split('/');
65
+ for (const segment of segments) {
66
+ if (segment.startsWith('namespace:')) {
67
+ parts.namespace = segment.substring(10);
68
+ } else if (segment.startsWith('task:')) {
69
+ parts.task = segment.substring(5);
70
+ }
71
+ }
72
+ return parts;
73
+ }
74
+ // ...
75
+ }
76
+ ```
77
+
78
+ ### 4. Defensive Filtering of Null Messages
79
+ **Issue**: Server might return null values in the `messages` array response.
80
+
81
+ **Location**: `src/client/client.js` in `take()` method
82
+
83
+ **Fix**: Filter out null/undefined messages when yielding:
84
+ ```javascript
85
+ // Yield each message (filter out any null/undefined values)
86
+ for (const message of result.messages) {
87
+ if (message) { // Skip null/undefined messages
88
+ yield message;
89
+ count++;
90
+ if (limit && count >= limit) return;
91
+ }
92
+ }
93
+ ```
94
+
95
+ ## Test Fixes
96
+
97
+ ### 1. Consumer Group Isolation Test
98
+ **Problem**: Test was ACKing all messages as completed for both groups, but the test expects group2 to have some failed messages to verify isolation.
99
+
100
+ **Solution**: Actually fail the first 2 messages for group2:
101
+ ```javascript
102
+ // Fail first 2 messages for group2, complete the rest
103
+ for (let i = 0; i < group2Messages.length; i++) {
104
+ if (i < 2) {
105
+ await client.ack(group2Messages[i], false, {
106
+ error: 'Test failure',
107
+ group: 'group2'
108
+ });
109
+ } else {
110
+ await client.ack(group2Messages[i], true, { group: 'group2' });
111
+ }
112
+ }
113
+ ```
114
+
115
+ ### 2. Empty and Null Payloads Test
116
+ **Problem**: If server returns null message objects in the array, the test would crash.
117
+
118
+ **Solution**: Add defensive check (though client now filters these):
119
+ ```javascript
120
+ for await (const msg of client.take(queue, { limit: 10 })) {
121
+ if (!msg) {
122
+ throw new Error('Received null/undefined message from take()');
123
+ }
124
+ messages.push(msg);
125
+ await client.ack(msg);
126
+ }
127
+ ```
128
+
129
+ ## Key Learnings
130
+
131
+ ### System is Transactional ✅
132
+
133
+ The Queen system is **transactional**. When you `await client.ack(msg)`:
134
+ - The ACK completes atomically
135
+ - The partition lock is released immediately
136
+ - No "timing issues" or delays needed
137
+
138
+ ### Correct Pattern
139
+ ```javascript
140
+ // ✅ This works correctly:
141
+ for await (const msg of client.take(queue, { limit: 10 })) {
142
+ await client.ack(msg); // Partition unlocked when this completes
143
+ }
144
+
145
+ // Next consumer can immediately access the partition:
146
+ for await (const msg of client.take(queue, { limit: 10 })) {
147
+ // Gets messages right away
148
+ }
149
+ ```
150
+
151
+ ### Edge Case Handling
152
+
153
+ 1. **Null payloads are valid**: Tests should validate that the system handles `null`, `undefined`, empty objects, and empty strings as payloads.
154
+
155
+ 2. **Client must be defensive**: Check for `typeof x === 'object' && x !== null` when you need to access properties.
156
+
157
+ 3. **Server responses should be filtered**: If server returns null messages in an array, filter them out client-side.
158
+
159
+ ### Test Design Principles
160
+
161
+ 1. **Test real edge cases**: null, undefined, empty strings, large payloads, etc.
162
+
163
+ 2. **Don't add artificial delays**: The system is transactional. If you need a delay, something is wrong.
164
+
165
+ 3. **Verify isolation**: When testing consumer groups, actually test that different groups can have different statuses for the same messages.
166
+
167
+ 4. **Use appropriate batch sizes**: Match batch size to expected message count for efficient testing.
168
+
169
+ ## Final Results
170
+
171
+ After fixing client bugs and test issues:
172
+ - 33/33 tests should pass
173
+ - No timing sensitivity
174
+ - Clean, transactional behavior throughout
@@ -0,0 +1,203 @@
1
+ # Queen Message Queue Test Suite
2
+
3
+ This directory contains the comprehensive test suite for the Queen Message Queue system, using the new minimalist `Queen` client interface.
4
+
5
+ ## Test Structure
6
+
7
+ The test suite is organized into focused, modular files:
8
+
9
+ ### Core Files
10
+
11
+ - **`test-new.js`** - Main test runner that orchestrates all tests
12
+ - **`utils.js`** - Shared utilities (logging, database helpers, result tracking)
13
+
14
+ ### Test Categories
15
+
16
+ #### 1. Core Features (`core-tests.js`)
17
+ - Queue creation policy
18
+ - Single/batch message push
19
+ - Queue configuration
20
+ - Take and acknowledgment
21
+ - Delayed processing
22
+ - FIFO ordering within partitions
23
+
24
+ #### 2. Partition Locking (`partition-locking-tests.js`)
25
+ - Partition locking in queue mode
26
+ - Partition locking in bus mode
27
+ - Specific partition requests with locking
28
+ - Namespace/task filtering with partition locking
29
+
30
+ #### 3. Enterprise Features (`enterprise-tests.js`)
31
+ - Message encryption/decryption
32
+ - Retention policies (pending & completed messages)
33
+ - Message eviction
34
+ - Combined enterprise features
35
+ - Enterprise error handling
36
+
37
+ #### 4. Bus Mode Features (`bus-mode-tests.js`)
38
+ - Consumer groups
39
+ - Mixed mode (queue + bus)
40
+ - Subscription modes (all vs new messages)
41
+ - Consumer group isolation
42
+
43
+ #### 5. Edge Cases (`edge-case-tests.js`)
44
+ - Empty and null payloads
45
+ - Very large payloads
46
+ - Concurrent push/take operations
47
+ - Retry limit exhaustion
48
+ - Lease expiration and redelivery
49
+ - SQL injection prevention
50
+ - XSS prevention
51
+
52
+ #### 6. Advanced Patterns (`advanced-pattern-tests.js`)
53
+ - Multi-stage pipeline workflow
54
+ - Fan-out/fan-in pattern
55
+ - Dead letter queue pattern
56
+ - Circuit breaker pattern
57
+ - Message deduplication
58
+
59
+ ## New vs Old Interface
60
+
61
+ ### Old Interface (test.js)
62
+ ```javascript
63
+ import { createQueenClient } from '../client/queenClient.js';
64
+
65
+ const client = createQueenClient({ baseUrls: [...] });
66
+
67
+ // Configure
68
+ await client.configure({ queue: 'myqueue', options: {} });
69
+
70
+ // Push
71
+ await client.push({ items: [{ queue: 'myqueue', partition: 'Default', payload: {...} }] });
72
+
73
+ // Pop
74
+ const result = await client.pop({ queue: 'myqueue', batch: 10 });
75
+
76
+ // Ack
77
+ await client.ack(transactionId, 'completed', null, consumerGroup);
78
+ ```
79
+
80
+ ### New Interface (test-new.js)
81
+ ```javascript
82
+ import { Queen } from '../client/client.js';
83
+
84
+ const client = new Queen({ baseUrls: [...] });
85
+
86
+ // Configure
87
+ await client.queue('myqueue', {}, { namespace, task });
88
+
89
+ // Push
90
+ await client.push('myqueue/partition', payload);
91
+
92
+ // Take (async iterator)
93
+ for await (const msg of client.take('myqueue/partition@group', { limit: 10 })) {
94
+ // Process message
95
+ await client.ack(msg, true, { group: 'mygroup' });
96
+ }
97
+ ```
98
+
99
+ ## Key Differences
100
+
101
+ 1. **Address Format**: The new interface uses a unified address format:
102
+ - `"queue"` - Simple queue
103
+ - `"queue/partition"` - Specific partition
104
+ - `"queue@group"` - Consumer group
105
+ - `"queue/partition@group"` - Partition + group
106
+ - `"namespace:name"` - Namespace filter
107
+ - `"task:name"` - Task filter
108
+ - `"namespace:billing/task:process"` - Combined filters
109
+
110
+ 2. **Async Iterator**: `pop()` → `take()` using async iteration
111
+ ```javascript
112
+ for await (const msg of client.take(address, options)) {
113
+ // Handle message
114
+ }
115
+ ```
116
+
117
+ 3. **Simplified Methods**: 4 core methods instead of many:
118
+ - `client.queue(name, options, metadata)` - Configure
119
+ - `client.push(address, payload, options)` - Send
120
+ - `client.take(address, options)` - Receive (async iterator)
121
+ - `client.ack(message, status, context)` - Acknowledge
122
+
123
+ 4. **Message Properties**: Can be in payload or options:
124
+ ```javascript
125
+ // In options
126
+ await client.push('queue', { data }, { transactionId: 'txn-1' });
127
+
128
+ // In payload
129
+ await client.push('queue', { data, transactionId: 'txn-1' });
130
+ ```
131
+
132
+ ## Running Tests
133
+
134
+ ```bash
135
+ # Make sure Queen servers are running first
136
+ # And database is accessible
137
+
138
+ cd /Users/alice/Work/queen
139
+
140
+ # Run all tests
141
+ nvm use 22 && node src/test/test-new.js
142
+
143
+ # Run specific test category
144
+ node src/test/test-new.js core # Core features only
145
+ node src/test/test-new.js partition # Partition locking tests only
146
+ node src/test/test-new.js enterprise # Enterprise features only
147
+ node src/test/test-new.js bus # Bus mode tests only
148
+ node src/test/test-new.js edge # Edge cases only
149
+ node src/test/test-new.js advanced # Advanced patterns only
150
+
151
+ # Show help
152
+ node src/test/test-new.js help
153
+ ```
154
+
155
+ ### Available Test Categories
156
+
157
+ - **`core`** - Core features (queue creation, push, take, ack, delayed processing, FIFO)
158
+ - **`partition`** (or `locking`) - Partition locking in queue and bus modes
159
+ - **`enterprise`** - Enterprise features (encryption, retention, eviction)
160
+ - **`bus`** - Bus mode features (consumer groups, mixed mode, subscription modes, isolation)
161
+ - **`edge`** - Edge cases (null payloads, large payloads, concurrency, SQL injection, XSS)
162
+ - **`advanced`** (or `pattern`) - Advanced patterns (pipeline, fan-out/fan-in, DLQ, circuit breaker, deduplication)
163
+
164
+ ## Test Configuration
165
+
166
+ Tests use environment variables for configuration:
167
+ - `PG_HOST` - PostgreSQL host (default: localhost)
168
+ - `PG_PORT` - PostgreSQL port (default: 5432)
169
+ - `PG_DB` - Database name (default: postgres)
170
+ - `PG_USER` - Database user (default: postgres)
171
+ - `PG_PASSWORD` - Database password (default: postgres)
172
+ - `QUEEN_ENCRYPTION_KEY` - Optional encryption key for encryption tests
173
+
174
+ ## Adding New Tests
175
+
176
+ 1. Create a test function in the appropriate category file:
177
+ ```javascript
178
+ export async function testMyFeature(client) {
179
+ startTest('My Feature Test', 'category');
180
+ try {
181
+ // Test code here
182
+ passTest('Feature works correctly');
183
+ } catch (error) {
184
+ failTest(error);
185
+ }
186
+ }
187
+ ```
188
+
189
+ 2. Import and add to `test-new.js`:
190
+ ```javascript
191
+ import { testMyFeature } from './category-tests.js';
192
+
193
+ // In runTests():
194
+ await runTest(() => testMyFeature(client));
195
+ ```
196
+
197
+ ## Notes
198
+
199
+ - All tests automatically clean up test data before and after running
200
+ - Tests use the `test-*`, `edge-*`, `pattern-*`, and `workflow-*` queue name prefixes
201
+ - The test suite supports both single-server and multi-server configurations
202
+ - Some enterprise tests (encryption, retention) are skipped if not configured
203
+