queen-mq 0.12.0 → 0.12.2

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.
@@ -1,201 +0,0 @@
1
- # Subscription Mode Tests
2
-
3
- Tests for consumer group subscription modes, compatible with any server `DEFAULT_SUBSCRIPTION_MODE` configuration.
4
-
5
- ## Test Overview
6
-
7
- | Test Function | Description | Explicit Mode Used |
8
- |--------------|-------------|-------------------|
9
- | `subscriptionModeNew` | Validates `.subscriptionMode('new')` skips historical messages | Yes (`new`) |
10
- | `subscriptionModeNewOnly` | Validates alias `.subscriptionMode('new-only')` | Yes (`new-only`) |
11
- | `subscriptionFromNow` | Validates `.subscriptionFrom('now')` | Yes (`now`) |
12
- | `subscriptionFromTimestamp` | Validates timestamp-based subscription | Yes (timestamp) |
13
- | `subscriptionModeAll` | Tests default behavior (depends on server config) | Mixed |
14
- | `subscriptionModeServerDefault` | **NEW:** Detects and validates server default | Mixed |
15
-
16
- ## Running the Tests
17
-
18
- ### Run all subscription tests:
19
- ```bash
20
- cd client-js/test-v2
21
- node run.js subscription
22
- ```
23
-
24
- ### Run specific subscription test:
25
- ```bash
26
- node run.js subscriptionModeNew
27
- node run.js subscriptionModeServerDefault
28
- ```
29
-
30
- ## Server Configuration Compatibility
31
-
32
- These tests work with **any** server `DEFAULT_SUBSCRIPTION_MODE` configuration:
33
-
34
- ### Configuration 1: Standard (Default)
35
-
36
- ```bash
37
- # Start server without default
38
- ./bin/queen-server
39
-
40
- # Run tests
41
- node run.js subscription
42
- ```
43
-
44
- **Expected Behavior:**
45
- - Consumer groups without explicit mode process all historical messages
46
- - Consumer groups with `.subscriptionMode('new')` skip history
47
- - `subscriptionModeServerDefault` reports: "all (or empty string)"
48
-
49
- ### Configuration 2: With DEFAULT_SUBSCRIPTION_MODE="new"
50
-
51
- ```bash
52
- # Start server with "new" as default
53
- DEFAULT_SUBSCRIPTION_MODE="new" ./bin/queen-server
54
-
55
- # Run tests
56
- node run.js subscription
57
- ```
58
-
59
- **Expected Behavior:**
60
- - Consumer groups without explicit mode skip historical messages
61
- - Consumer groups with `.subscriptionMode('new')` skip history (same)
62
- - `subscriptionModeServerDefault` reports: "new"
63
-
64
- **✅ All tests pass with both configurations!**
65
-
66
- ## Test Details
67
-
68
- ### subscriptionModeNew
69
-
70
- Tests the explicit `.subscriptionMode('new')` behavior:
71
-
72
- 1. Pushes 5 historical messages
73
- 2. Creates two consumer groups:
74
- - One without explicit mode (behavior depends on server)
75
- - One with `.subscriptionMode('new')` (always skips)
76
- 3. Verifies the `new` mode group gets 0 historical messages
77
- 4. Pushes 3 new messages
78
- 5. Verifies both groups get the new messages
79
-
80
- **Key Assertion:** `.subscriptionMode('new')` always skips historical messages.
81
-
82
- ### subscriptionModeServerDefault (NEW)
83
-
84
- Detects and validates the server's default subscription mode:
85
-
86
- 1. Pushes historical messages
87
- 2. Creates consumer group WITHOUT explicit subscription mode
88
- 3. Detects server default based on behavior:
89
- - Got historical messages → server default is "all"
90
- - Got 0 messages → server default is "new"
91
- 4. Validates explicit `.subscriptionMode('new')` still works
92
- 5. Verifies new messages are received by both groups
93
-
94
- **Key Assertion:** Server default affects groups without explicit mode, but explicit modes always work.
95
-
96
- ### subscriptionFromTimestamp
97
-
98
- Tests timestamp-based subscription:
99
-
100
- 1. Pushes "first batch" of messages
101
- 2. Records cutoff timestamp
102
- 3. Pushes "second batch" of messages
103
- 4. Creates consumer group with `.subscriptionFrom(cutoffTimestamp)`
104
- 5. Verifies only messages after timestamp are received
105
-
106
- **Note:** Timestamp precision may vary based on database clock.
107
-
108
- ## Common Issues
109
-
110
- ### Issue: Test fails with "expected X, got Y"
111
-
112
- **Cause:** Server has different default than test expects.
113
-
114
- **Solution:** Check server configuration:
115
- ```bash
116
- # Check if DEFAULT_SUBSCRIPTION_MODE is set
117
- echo $DEFAULT_SUBSCRIPTION_MODE
118
-
119
- # Check server logs on startup
120
- LOG_LEVEL=debug ./bin/queen-server | grep "default subscription"
121
- ```
122
-
123
- ### Issue: Consumer group exists from previous run
124
-
125
- **Cause:** Consumer groups persist between test runs.
126
-
127
- **Solution:** The test runner cleans up test data automatically:
128
- ```javascript
129
- await dbPool.query(`DELETE FROM queen.queues WHERE name LIKE 'test-%'`)
130
- ```
131
-
132
- Or manually:
133
- ```sql
134
- DELETE FROM queen.partition_consumers
135
- WHERE consumer_group LIKE 'group-%';
136
- ```
137
-
138
- ### Issue: Timing-related test failures
139
-
140
- **Cause:** Messages not fully persisted before consumption.
141
-
142
- **Solution:** Tests include appropriate delays. If still failing, increase delays:
143
- ```javascript
144
- await new Promise(resolve => setTimeout(resolve, 500)) // Increase if needed
145
- ```
146
-
147
- ## Adding New Subscription Tests
148
-
149
- When adding new subscription mode tests:
150
-
151
- 1. **Use unique queue/group names** to avoid conflicts
152
- 2. **Be explicit about subscription modes** in test assertions
153
- 3. **Document expected behavior** for both server configurations
154
- 4. **Use descriptive group names** like `group-explicit-new` vs `group-default`
155
-
156
- Example:
157
- ```javascript
158
- export async function testNewFeature(client) {
159
- // Setup
160
- await client.queue('test-new-feature').create()
161
-
162
- // Test explicit behavior (works with any server default)
163
- const result = await client
164
- .queue('test-new-feature')
165
- .group('group-explicit-new')
166
- .subscriptionMode('new') // Explicit!
167
- .pop()
168
-
169
- // Assert based on explicit mode, not server default
170
- // ...
171
- }
172
- ```
173
-
174
- ## Debugging Failed Tests
175
-
176
- Enable detailed logging:
177
-
178
- ```bash
179
- # Client logging
180
- QUEEN_CLIENT_LOG=true node run.js subscriptionModeNew
181
-
182
- # Server logging
183
- LOG_LEVEL=debug ./bin/queen-server
184
- ```
185
-
186
- Look for these log lines:
187
- ```
188
- Consumer group 'group-xxx' exists: false, has subscription options: true
189
- Subscription mode 'new' - starting from latest message: <uuid>
190
- Applying default subscription mode 'new' for consumer group 'group-yyy'
191
- ```
192
-
193
- ## Summary
194
-
195
- - ✅ Tests work with any `DEFAULT_SUBSCRIPTION_MODE` configuration
196
- - ✅ Explicit subscription modes are always tested and validated
197
- - ✅ Server default behavior is detected and reported
198
- - ✅ All tests are documented and maintainable
199
-
200
- Run the tests and see them pass! 🎉
201
-
@@ -1,194 +0,0 @@
1
- /**
2
- * Client-Side Buffering Tests
3
- * Tests for buffer management and statistics
4
- */
5
-
6
- export async function testBufferStatistics(client) {
7
- const queue = await client.queue('test-queue-v2-buffer-stats').create()
8
- if (!queue.configured) {
9
- return { success: false, message: 'Queue not created' }
10
- }
11
-
12
- // Get initial stats
13
- const statsBefore = client.getBufferStats()
14
-
15
- if (!statsBefore || typeof statsBefore !== 'object') {
16
- return { success: false, message: 'Buffer stats not available' }
17
- }
18
-
19
- // Push some buffered messages
20
- for (let i = 0; i < 5; i++) {
21
- await client.queue('test-queue-v2-buffer-stats')
22
- .buffer({ messageCount: 10, timeMillis: 5000 })
23
- .push([{ data: { id: i } }])
24
- }
25
-
26
- // Get stats after buffering
27
- const statsAfter = client.getBufferStats()
28
-
29
- // Stats should show buffer activity
30
- return {
31
- success: statsAfter !== null,
32
- message: `Buffer stats retrieved: ${JSON.stringify(statsAfter)}`
33
- }
34
- }
35
-
36
- export async function testManualBufferFlush(client) {
37
- const queue = await client.queue('test-queue-v2-manual-flush').create()
38
- if (!queue.configured) {
39
- return { success: false, message: 'Queue not created' }
40
- }
41
-
42
- // Push buffered messages (with high threshold so they don't auto-flush)
43
- for (let i = 0; i < 5; i++) {
44
- await client.queue('test-queue-v2-manual-flush')
45
- .buffer({ messageCount: 100, timeMillis: 30000 })
46
- .push([{ data: { id: i } }])
47
- }
48
-
49
- // Check that messages are not in queue yet
50
- const beforeFlush = await client.queue('test-queue-v2-manual-flush')
51
- .batch(10)
52
- .wait(false)
53
- .pop()
54
-
55
- if (beforeFlush.length > 0) {
56
- return { success: false, message: 'Messages not buffered (flushed too early)' }
57
- }
58
-
59
- // Manually flush all buffers
60
- await client.flushAllBuffers()
61
-
62
- // Now messages should be in the queue
63
- const afterFlush = await client.queue('test-queue-v2-manual-flush')
64
- .batch(10)
65
- .wait(false)
66
- .pop()
67
-
68
- return {
69
- success: afterFlush.length === 5,
70
- message: `Manual flush successful: ${afterFlush.length} messages`
71
- }
72
- }
73
-
74
- export async function testBufferTimeThreshold(client) {
75
- const queue = await client.queue('test-queue-v2-buffer-time').create()
76
- if (!queue.configured) {
77
- return { success: false, message: 'Queue not created' }
78
- }
79
-
80
- // Push with time-based buffer (1 second)
81
- await client.queue('test-queue-v2-buffer-time')
82
- .buffer({ messageCount: 1000, timeMillis: 1000 })
83
- .push([{ data: { test: 'data' } }])
84
-
85
- // Wait for time threshold
86
- await new Promise(resolve => setTimeout(resolve, 1500))
87
-
88
- // Message should be auto-flushed by time
89
- const messages = await client.queue('test-queue-v2-buffer-time')
90
- .batch(1)
91
- .wait(false)
92
- .pop()
93
-
94
- return {
95
- success: messages.length === 1,
96
- message: 'Time-based buffer flush works'
97
- }
98
- }
99
-
100
- export async function testBufferCountThreshold(client) {
101
- const queue = await client.queue('test-queue-v2-buffer-count').create()
102
- if (!queue.configured) {
103
- return { success: false, message: 'Queue not created' }
104
- }
105
-
106
- // Push exactly 5 messages with count threshold of 5
107
- for (let i = 0; i < 5; i++) {
108
- await client.queue('test-queue-v2-buffer-count')
109
- .buffer({ messageCount: 5, timeMillis: 30000 })
110
- .push([{ data: { id: i } }])
111
- }
112
-
113
- // Small delay for flush to process
114
- await new Promise(resolve => setTimeout(resolve, 200))
115
-
116
- // All 5 should be flushed when threshold reached
117
- const messages = await client.queue('test-queue-v2-buffer-count')
118
- .batch(10)
119
- .wait(false)
120
- .pop()
121
-
122
- return {
123
- success: messages.length === 5,
124
- message: `Count-based buffer flush: ${messages.length} messages`
125
- }
126
- }
127
-
128
- export async function testMultipleBuffers(client) {
129
- // Create multiple queues with buffering
130
- await client.queue('test-queue-v2-multi-buf-1').create()
131
- await client.queue('test-queue-v2-multi-buf-2').create()
132
- await client.queue('test-queue-v2-multi-buf-3').create()
133
-
134
- // Buffer messages to different queues
135
- for (let i = 0; i < 3; i++) {
136
- await client.queue('test-queue-v2-multi-buf-1')
137
- .buffer({ messageCount: 100, timeMillis: 10000 })
138
- .push([{ data: { queue: 1, id: i } }])
139
-
140
- await client.queue('test-queue-v2-multi-buf-2')
141
- .buffer({ messageCount: 100, timeMillis: 10000 })
142
- .push([{ data: { queue: 2, id: i } }])
143
-
144
- await client.queue('test-queue-v2-multi-buf-3')
145
- .buffer({ messageCount: 100, timeMillis: 10000 })
146
- .push([{ data: { queue: 3, id: i } }])
147
- }
148
-
149
- // Get stats to see multiple buffers
150
- const stats = client.getBufferStats()
151
-
152
- // Flush all buffers
153
- await client.flushAllBuffers()
154
-
155
- // Verify all queues received messages
156
- const msgs1 = await client.queue('test-queue-v2-multi-buf-1').batch(10).wait(false).pop()
157
- const msgs2 = await client.queue('test-queue-v2-multi-buf-2').batch(10).wait(false).pop()
158
- const msgs3 = await client.queue('test-queue-v2-multi-buf-3').batch(10).wait(false).pop()
159
-
160
- return {
161
- success: msgs1.length === 3 && msgs2.length === 3 && msgs3.length === 3,
162
- message: `Multiple buffers flushed: ${msgs1.length + msgs2.length + msgs3.length} total`
163
- }
164
- }
165
-
166
- export async function testBufferWithPartition(client) {
167
- const queue = await client.queue('test-queue-v2-buffer-partition').create()
168
- if (!queue.configured) {
169
- return { success: false, message: 'Queue not created' }
170
- }
171
-
172
- // Buffer messages to specific partition
173
- for (let i = 0; i < 5; i++) {
174
- await client.queue('test-queue-v2-buffer-partition')
175
- .partition('test-partition-buf')
176
- .buffer({ messageCount: 100, timeMillis: 10000 })
177
- .push([{ data: { id: i } }])
178
- }
179
-
180
- await client.flushAllBuffers()
181
-
182
- // Pop from that partition
183
- const messages = await client.queue('test-queue-v2-buffer-partition')
184
- .partition('test-partition-buf')
185
- .batch(10)
186
- .wait(false)
187
- .pop()
188
-
189
- return {
190
- success: messages.length === 5,
191
- message: 'Buffering with partition works'
192
- }
193
- }
194
-
@@ -1,223 +0,0 @@
1
- /**
2
- * Error Handling & Edge Cases Tests
3
- * Tests various error conditions and edge cases
4
- */
5
-
6
- export async function testInvalidQueueName(client) {
7
- try {
8
- await client.queue('').create()
9
- return { success: false, message: 'Empty queue name should fail' }
10
- } catch (error) {
11
- return { success: true, message: 'Empty queue name correctly rejected' }
12
- }
13
- }
14
-
15
- export async function testInvalidConfiguration(client) {
16
- try {
17
- await client.queue('test-queue-invalid-config')
18
- .config({ leaseTime: -1 })
19
- .create()
20
- return { success: false, message: 'Negative leaseTime should fail' }
21
- } catch (error) {
22
- return { success: true, message: 'Invalid config correctly rejected' }
23
- }
24
- }
25
-
26
- export async function testInvalidMessageFormat(client) {
27
- const queue = await client.queue('test-queue-v2-invalid-msg').create()
28
- if (!queue.configured) {
29
- return { success: false, message: 'Queue not created' }
30
- }
31
-
32
- try {
33
- // Push without data property
34
- await client.queue('test-queue-v2-invalid-msg').push([{ invalid: 'structure' }])
35
- // If it doesn't throw, check if it still works
36
- return { success: true, message: 'Client handles flexible message format' }
37
- } catch (error) {
38
- return { success: true, message: 'Invalid message format correctly handled' }
39
- }
40
- }
41
-
42
- export async function testPushEmptyArray(client) {
43
- const queue = await client.queue('test-queue-v2-empty-array').create()
44
- if (!queue.configured) {
45
- return { success: false, message: 'Queue not created' }
46
- }
47
-
48
- const result = await client.queue('test-queue-v2-empty-array').push([])
49
- return { success: result.length === 0, message: 'Empty array push handled correctly' }
50
- }
51
-
52
- export async function testAckWithoutPartitionId(client) {
53
- const queue = await client.queue('test-queue-v2-ack-no-partition').create()
54
- if (!queue.configured) {
55
- return { success: false, message: 'Queue not created' }
56
- }
57
-
58
- await client.queue('test-queue-v2-ack-no-partition')
59
- .push([{ data: { test: 'data' } }])
60
-
61
- const messages = await client.queue('test-queue-v2-ack-no-partition')
62
- .batch(1)
63
- .wait(false)
64
- .pop()
65
-
66
- if (messages.length === 0) {
67
- return { success: false, message: 'No message to test' }
68
- }
69
-
70
- // Try to ack without partitionId (should fail)
71
- const messageWithoutPartition = { transactionId: messages[0].transactionId }
72
- const result = await client.ack(messageWithoutPartition)
73
-
74
- return {
75
- success: result.success === false && result.error.includes('partitionId'),
76
- message: 'Ack without partitionId correctly rejected'
77
- }
78
- }
79
-
80
- export async function testPopFromNonExistentQueue(client) {
81
- try {
82
- const result = await client
83
- .queue('non-existent-queue-xyz')
84
- .batch(1)
85
- .wait(false)
86
- .pop()
87
-
88
- // It's okay if it returns empty or throws
89
- return { success: true, message: 'Non-existent queue handled gracefully' }
90
- } catch (error) {
91
- return { success: true, message: 'Non-existent queue error handled' }
92
- }
93
- }
94
-
95
- export async function testMultiplePushesWithSameTransactionId(client) {
96
- const queue = await client.queue('test-queue-v2-same-txid').create()
97
- if (!queue.configured) {
98
- return { success: false, message: 'Queue not created' }
99
- }
100
-
101
- const txId = 'duplicate-transaction-id-test'
102
-
103
- const result1 = await client.queue('test-queue-v2-same-txid')
104
- .push([{ transactionId: txId, data: { first: true } }])
105
-
106
- const result2 = await client.queue('test-queue-v2-same-txid')
107
- .push([{ transactionId: txId, data: { second: true } }])
108
-
109
- const result3 = await client.queue('test-queue-v2-same-txid')
110
- .push([{ transactionId: txId, data: { third: true } }])
111
-
112
- const duplicates = [result1[0], result2[0], result3[0]].filter(r => r.status === 'duplicate').length
113
-
114
- return {
115
- success: duplicates >= 2,
116
- message: `Duplicate detection working (${duplicates} duplicates found)`
117
- }
118
- }
119
-
120
- export async function testAckExpiredLease(client) {
121
- const queue = await client.queue('test-queue-v2-expired-lease')
122
- .config({ leaseTime: 1 })
123
- .create()
124
-
125
- if (!queue.configured) {
126
- return { success: false, message: 'Queue not created' }
127
- }
128
-
129
- await client.queue('test-queue-v2-expired-lease')
130
- .push([{ data: { test: 'data' } }])
131
-
132
- const messages = await client.queue('test-queue-v2-expired-lease')
133
- .batch(1)
134
- .wait(false)
135
- .pop()
136
-
137
- if (messages.length === 0) {
138
- return { success: false, message: 'No message to test' }
139
- }
140
-
141
- // Wait for lease to expire
142
- await new Promise(resolve => setTimeout(resolve, 2000))
143
-
144
- // Try to ack expired lease
145
- const result = await client.ack(messages[0], true)
146
-
147
- return {
148
- success: result.success === false,
149
- message: 'Expired lease ack correctly rejected'
150
- }
151
- }
152
-
153
- export async function testBatchAckMixedResults(client) {
154
- const queue = await client.queue('test-queue-v2-batch-ack-mixed')
155
- .config({ leaseTime: 2 })
156
- .create()
157
-
158
- if (!queue.configured) {
159
- return { success: false, message: 'Queue not created' }
160
- }
161
-
162
- await client.queue('test-queue-v2-batch-ack-mixed').push([
163
- { data: { id: 1 } },
164
- { data: { id: 2 } },
165
- { data: { id: 3 } }
166
- ])
167
-
168
- const messages = await client.queue('test-queue-v2-batch-ack-mixed')
169
- .batch(3)
170
- .wait(false)
171
- .pop()
172
-
173
- if (messages.length !== 3) {
174
- return { success: false, message: 'Expected 3 messages' }
175
- }
176
-
177
- // Wait for one lease to expire
178
- await new Promise(resolve => setTimeout(resolve, 2500))
179
-
180
- // Try to ack all three (one should fail due to expired lease)
181
- const result = await client.ack(messages, true)
182
-
183
- // Should indicate some failures
184
- return {
185
- success: result.success === true, // Batch ack returns overall success
186
- message: 'Batch ack with expired lease handled'
187
- }
188
- }
189
-
190
- export async function testVeryLargePayload(client) {
191
- const queue = await client.queue('test-queue-v2-very-large').create()
192
- if (!queue.configured) {
193
- return { success: false, message: 'Queue not created' }
194
- }
195
-
196
- // Create a very large payload (attempt to push near limits)
197
- const hugeArray = new Array(50000).fill({
198
- id: Math.random().toString(36),
199
- data: 'x'.repeat(100)
200
- })
201
-
202
- try {
203
- const result = await client.queue('test-queue-v2-very-large')
204
- .push([{ data: hugeArray }])
205
-
206
- const received = await client.queue('test-queue-v2-very-large')
207
- .batch(1)
208
- .wait(false)
209
- .pop()
210
-
211
- return {
212
- success: received.length === 1 && received[0].data.length === 50000,
213
- message: 'Very large payload handled correctly'
214
- }
215
- } catch (error) {
216
- // It's okay if it fails - system has limits
217
- return {
218
- success: true,
219
- message: `Large payload limit enforced: ${error.message}`
220
- }
221
- }
222
- }
223
-