Skip to content

Commit 2229d1a

Browse files
authored
chore(NODE-3719,NODE-3883): identify causal consistency spec tests (#3112)
1 parent 1ab98f4 commit 2229d1a

File tree

2 files changed

+259
-213
lines changed

2 files changed

+259
-213
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
'use strict';
2+
3+
const { LEGACY_HELLO_COMMAND } = require('../../../src/constants');
4+
5+
const { setupDatabase } = require('../shared');
6+
const { expect } = require('chai');
7+
8+
const ignoredCommands = [LEGACY_HELLO_COMMAND, 'endSessions'];
9+
const test = { commands: { started: [], succeeded: [] } };
10+
11+
// TODO(NODE-3882) - properly implement all prose tests and add missing cases 1, 8, 9, 11, 12
12+
describe('Causal Consistency - prose tests', function () {
13+
before(function () {
14+
return setupDatabase(this.configuration);
15+
});
16+
17+
beforeEach(function () {
18+
test.commands = { started: [], succeeded: [] };
19+
test.client = this.configuration.newClient({ w: 1 }, { maxPoolSize: 1, monitorCommands: true });
20+
test.client.on('commandStarted', event => {
21+
if (ignoredCommands.indexOf(event.commandName) === -1) test.commands.started.push(event);
22+
});
23+
24+
test.client.on('commandSucceeded', event => {
25+
if (ignoredCommands.indexOf(event.commandName) === -1) test.commands.succeeded.push(event);
26+
});
27+
28+
return test.client.connect();
29+
});
30+
31+
afterEach(() => {
32+
return test.client.close();
33+
});
34+
35+
it(
36+
'2. The first read in a causally consistent session must not send afterClusterTime to the server',
37+
/**
38+
* session = client.startSession(causalConsistency = true)
39+
* document = collection.anyReadOperation(session, ...)
40+
* capture the command sent to the server (using APM or other mechanism)
41+
* assert that the command does not have an afterClusterTime
42+
*/
43+
{
44+
metadata: {
45+
requires: { topology: ['replicaset', 'sharded'] },
46+
// Skipping session leak tests b/c these are explicit sessions
47+
sessions: { skipLeakTests: true }
48+
},
49+
50+
test: function () {
51+
const session = test.client.startSession({ causalConsistency: true });
52+
const db = test.client.db(this.configuration.db);
53+
54+
return db
55+
.collection('causal_test')
56+
.findOne({}, { session: session })
57+
.then(() => {
58+
expect(test.commands.started).to.have.length(1);
59+
expect(test.commands.succeeded).to.have.length(1);
60+
61+
const findCommand = test.commands.started[0].command;
62+
expect(findCommand).to.have.property('find', 'causal_test');
63+
expect(findCommand).to.not.have.key('readConcern');
64+
});
65+
}
66+
}
67+
);
68+
69+
// TODO(NODE-3882): need to do this for one write in addition to the read; and also test with errors
70+
// TODO(NODE-3882): we also need to run this test without causal consistency
71+
context(
72+
'3. The first read or write on a ClientSession should update the operationTime of the ClientSession, even if there is an error',
73+
() => {
74+
/**
75+
* session = client.startSession() // with or without causal consistency
76+
* collection.anyReadOrWriteOperation(session, ...) // test with errors also if possible
77+
* capture the response sent from the server (using APM or other mechanism)
78+
* assert session.operationTime has the same value that is in the response from the server
79+
*/
80+
81+
it('case: successful read with causal consistency', {
82+
metadata: {
83+
requires: { topology: ['replicaset', 'sharded'] },
84+
// Skipping session leak tests b/c these are explicit sessions
85+
sessions: { skipLeakTests: true }
86+
},
87+
88+
test: function () {
89+
const session = test.client.startSession({ causalConsistency: true });
90+
const db = test.client.db(this.configuration.db);
91+
expect(session.operationTime).to.not.exist;
92+
93+
return db
94+
.collection('causal_test')
95+
.findOne({}, { session: session })
96+
.then(() => {
97+
expect(test.commands.started).to.have.length(1);
98+
expect(test.commands.succeeded).to.have.length(1);
99+
100+
const lastReply = test.commands.succeeded[0].reply;
101+
const maybeLong = val => (typeof val.equals === 'function' ? val.toNumber() : val);
102+
expect(maybeLong(session.operationTime)).to.equal(maybeLong(lastReply.operationTime));
103+
});
104+
}
105+
});
106+
}
107+
);
108+
109+
// TODO(NODE-3882): this should be repeated for all potential read operations
110+
context(
111+
'4. A findOne followed by any other read operation should include the operationTime returned by the server for the first operation in the afterClusterTime parameter of the second operation',
112+
/**
113+
* session = client.startSession(causalConsistency = true) * collection.findOne(session, {})
114+
* operationTime = session.operationTime * collection.anyReadOperation(session, ...)
115+
* capture the command sent to the server (using APM or other mechanism)
116+
* assert that the command has an afterClusterTime field with a value of operationTime
117+
*/
118+
() => {
119+
it('case: second operation is findOne', {
120+
metadata: {
121+
requires: { topology: ['replicaset', 'sharded'] },
122+
// Skipping session leak tests b/c these are explicit sessions
123+
sessions: { skipLeakTests: true }
124+
},
125+
126+
test: function () {
127+
const session = test.client.startSession({ causalConsistency: true });
128+
const db = test.client.db(this.configuration.db);
129+
expect(session.operationTime).to.not.exist;
130+
131+
let firstOperationTime;
132+
return db
133+
.collection('causal_test')
134+
.findOne({}, { session: session })
135+
.then(() => {
136+
const firstFindCommand = test.commands.started[0].command;
137+
expect(firstFindCommand).to.not.have.key('readConcern');
138+
firstOperationTime = test.commands.succeeded[0].reply.operationTime;
139+
140+
return db.collection('causal_test').findOne({}, { session: session });
141+
})
142+
.then(() => {
143+
const secondFindCommand = test.commands.started[1].command;
144+
expect(secondFindCommand).to.have.any.key('readConcern');
145+
expect(secondFindCommand.readConcern).to.have.any.key('afterClusterTime');
146+
expect(secondFindCommand.readConcern.afterClusterTime).to.eql(firstOperationTime);
147+
});
148+
}
149+
});
150+
}
151+
);
152+
153+
// TODO(NODE-3882): implement this for all write operations including the case where the first operation returned an error
154+
context(
155+
'5. Any write operation followed by a findOne operation should include the operationTime of the first operation in the afterClusterTime parameter of the second operation',
156+
/**
157+
* session = client.startSession(causalConsistency = true)
158+
* collection.anyWriteOperation(session, ...) // test with errors also where possible
159+
* operationTime = session.operationTime * collection.findOne(session, {})
160+
* capture the command sent to the server (using APM or other mechanism)
161+
* assert that the command has an afterClusterTime field with a value of operationTime
162+
*/
163+
() => {
164+
it('case: successful insert', {
165+
metadata: {
166+
requires: { topology: ['replicaset', 'sharded'] },
167+
// Skipping session leak tests b/c these are explicit sessions
168+
sessions: { skipLeakTests: true }
169+
},
170+
171+
test: function () {
172+
const session = test.client.startSession({ causalConsistency: true });
173+
const db = test.client.db(this.configuration.db);
174+
expect(session.operationTime).to.not.exist;
175+
176+
let firstOperationTime;
177+
return db
178+
.collection('causal_test')
179+
.insert({}, { session: session })
180+
.then(() => {
181+
firstOperationTime = test.commands.succeeded[0].reply.operationTime;
182+
return db.collection('causal_test').findOne({}, { session: session });
183+
})
184+
.then(() => {
185+
const secondFindCommand = test.commands.started[1].command;
186+
expect(secondFindCommand).to.have.any.key('readConcern');
187+
expect(secondFindCommand.readConcern).to.have.any.key('afterClusterTime');
188+
expect(secondFindCommand.readConcern.afterClusterTime).to.eql(firstOperationTime);
189+
});
190+
}
191+
});
192+
}
193+
);
194+
195+
it(
196+
'6. A read operation in a ClientSession that is not causally consistent should not include the afterClusterTime parameter in the command sent to the server',
197+
/**
198+
* session = client.startSession(causalConsistency = false)
199+
* collection.anyReadOperation(session, {})
200+
* operationTime = session.operationTime
201+
* capture the command sent to the server (using APM or other mechanism)
202+
* assert that the command does not have an afterClusterTime field
203+
*/
204+
{
205+
metadata: {
206+
requires: { topology: ['replicaset', 'sharded'] },
207+
// Skipping session leak tests b/c these are explicit sessions
208+
sessions: { skipLeakTests: true }
209+
},
210+
211+
test: function () {
212+
const session = test.client.startSession({ causalConsistency: false });
213+
const db = test.client.db(this.configuration.db);
214+
const coll = db.collection('causal_test', { readConcern: { level: 'majority' } });
215+
216+
return coll
217+
.findOne({}, { session: session })
218+
.then(() => coll.findOne({}, { session: session }))
219+
.then(() => {
220+
const commands = test.commands.started.map(command => command.command);
221+
expect(commands).to.have.length(2);
222+
for (const command of commands) {
223+
expect(command).to.have.any.key('readConcern');
224+
expect(command.readConcern).to.not.have.any.key('afterClusterTime');
225+
}
226+
});
227+
}
228+
}
229+
);
230+
231+
it(
232+
'7. A read operation in a causally consistent session against a deployment that does not support cluster times does not include the afterClusterTime parameter in the command sent to the server',
233+
/**
234+
* session = client.startSession(causalConsistency = true)
235+
* collection.anyReadOperation(session, {})
236+
* capture the command sent to the server (using APM or other mechanism)
237+
* assert that the command does not have an afterClusterTime field
238+
*/
239+
{
240+
metadata: { requires: { topology: ['single'] } },
241+
242+
test: function () {
243+
const db = test.client.db(this.configuration.db);
244+
const coll = db.collection('causal_test', { readConcern: { level: 'local' } });
245+
246+
return coll
247+
.findOne({})
248+
.then(() => coll.findOne({}))
249+
.then(() => {
250+
const command = test.commands.started[1].command;
251+
expect(command).to.have.any.key('readConcern');
252+
expect(command.readConcern).to.not.have.any.key('afterClusterTime');
253+
});
254+
}
255+
}
256+
);
257+
258+
// #10 is removed by DRIVERS-1374/NODE-3883
259+
});

0 commit comments

Comments
 (0)