@objectql/driver-mongo 4.0.2 → 4.0.3
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.
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +16 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +132 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +174 -3
- package/test/integration.test.ts +250 -5
- package/tsconfig.tsbuildinfo +1 -1
package/test/integration.test.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { MongoDriver } from '../src';
|
|
10
10
|
import { MongoClient } from 'mongodb';
|
|
11
|
-
import {
|
|
11
|
+
import { MongoMemoryReplSet } from 'mongodb-memory-server';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Integration tests for MongoDriver with real MongoDB operations.
|
|
@@ -32,18 +32,22 @@ const skipIfMongoUnavailable = () => {
|
|
|
32
32
|
describe('MongoDriver Integration Tests', () => {
|
|
33
33
|
let driver: MongoDriver;
|
|
34
34
|
let client: MongoClient;
|
|
35
|
-
let mongod:
|
|
35
|
+
let mongod: MongoMemoryReplSet;
|
|
36
36
|
let mongoUrl: string;
|
|
37
37
|
let dbName: string;
|
|
38
38
|
|
|
39
39
|
beforeAll(async () => {
|
|
40
40
|
try {
|
|
41
|
-
// Use existing MONGO_URL if provided (e.g.
|
|
42
|
-
// Otherwise start an in-memory
|
|
41
|
+
// Use existing MONGO_URL if provided (e.g. in CI services)
|
|
42
|
+
// Otherwise start an in-memory replica set
|
|
43
43
|
if (process.env.MONGO_URL) {
|
|
44
44
|
mongoUrl = process.env.MONGO_URL;
|
|
45
45
|
} else {
|
|
46
|
-
|
|
46
|
+
// Create MongoDB Memory Replica Set
|
|
47
|
+
// This is required for change streams to work
|
|
48
|
+
mongod = await MongoMemoryReplSet.create({
|
|
49
|
+
replSet: { count: 1, storageEngine: 'wiredTiger' }
|
|
50
|
+
});
|
|
47
51
|
mongoUrl = mongod.getUri();
|
|
48
52
|
}
|
|
49
53
|
|
|
@@ -716,4 +720,245 @@ describe('MongoDriver Integration Tests', () => {
|
|
|
716
720
|
expect(results.length).toBe(2);
|
|
717
721
|
});
|
|
718
722
|
});
|
|
723
|
+
|
|
724
|
+
describe('Distinct Method', () => {
|
|
725
|
+
beforeEach(async () => {
|
|
726
|
+
if (skipIfMongoUnavailable()) return;
|
|
727
|
+
// Create test data
|
|
728
|
+
await driver.create('products', { name: 'Laptop', category: 'Electronics', price: 1200 });
|
|
729
|
+
await driver.create('products', { name: 'Mouse', category: 'Electronics', price: 25 });
|
|
730
|
+
await driver.create('products', { name: 'Desk', category: 'Furniture', price: 500 });
|
|
731
|
+
await driver.create('products', { name: 'Chair', category: 'Furniture', price: 300 });
|
|
732
|
+
await driver.create('products', { name: 'Monitor', category: 'Electronics', price: 400 });
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
test('should get distinct values for a field', async () => {
|
|
736
|
+
if (skipIfMongoUnavailable()) return;
|
|
737
|
+
|
|
738
|
+
const categories = await driver.distinct('products', 'category');
|
|
739
|
+
|
|
740
|
+
expect(categories).toBeDefined();
|
|
741
|
+
expect(Array.isArray(categories)).toBe(true);
|
|
742
|
+
expect(categories.sort()).toEqual(['Electronics', 'Furniture'].sort());
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
test('should get distinct values with filters', async () => {
|
|
746
|
+
if (skipIfMongoUnavailable()) return;
|
|
747
|
+
|
|
748
|
+
const names = await driver.distinct('products', 'name', {
|
|
749
|
+
category: 'Electronics'
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
expect(names).toBeDefined();
|
|
753
|
+
expect(names.length).toBe(3);
|
|
754
|
+
expect(names).toContain('Laptop');
|
|
755
|
+
expect(names).toContain('Mouse');
|
|
756
|
+
expect(names).toContain('Monitor');
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
test('should return empty array for non-matching filters', async () => {
|
|
760
|
+
if (skipIfMongoUnavailable()) return;
|
|
761
|
+
|
|
762
|
+
const values = await driver.distinct('products', 'name', {
|
|
763
|
+
category: 'NonExistent'
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
expect(values).toEqual([]);
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
describe('FindOneAndUpdate Method', () => {
|
|
771
|
+
beforeEach(async () => {
|
|
772
|
+
if (skipIfMongoUnavailable()) return;
|
|
773
|
+
// Create test data
|
|
774
|
+
await driver.create('users', { name: 'Alice', email: 'alice@example.com', status: 'active', points: 100 });
|
|
775
|
+
await driver.create('users', { name: 'Bob', email: 'bob@example.com', status: 'inactive', points: 50 });
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
test('should find and update a document', async () => {
|
|
779
|
+
if (skipIfMongoUnavailable()) return;
|
|
780
|
+
|
|
781
|
+
const result = await driver.findOneAndUpdate(
|
|
782
|
+
'users',
|
|
783
|
+
{ email: 'alice@example.com' },
|
|
784
|
+
{ $set: { status: 'premium' } },
|
|
785
|
+
{ returnDocument: 'after' }
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
expect(result).toBeDefined();
|
|
789
|
+
expect(result.email).toBe('alice@example.com');
|
|
790
|
+
expect(result.status).toBe('premium');
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
test('should return document before update', async () => {
|
|
794
|
+
if (skipIfMongoUnavailable()) return;
|
|
795
|
+
|
|
796
|
+
const result = await driver.findOneAndUpdate(
|
|
797
|
+
'users',
|
|
798
|
+
{ email: 'bob@example.com' },
|
|
799
|
+
{ $set: { status: 'active' } },
|
|
800
|
+
{ returnDocument: 'before' }
|
|
801
|
+
);
|
|
802
|
+
|
|
803
|
+
expect(result).toBeDefined();
|
|
804
|
+
expect(result.status).toBe('inactive'); // Should be the old value
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
test('should increment a field atomically', async () => {
|
|
808
|
+
if (skipIfMongoUnavailable()) return;
|
|
809
|
+
|
|
810
|
+
const result = await driver.findOneAndUpdate(
|
|
811
|
+
'users',
|
|
812
|
+
{ email: 'alice@example.com' },
|
|
813
|
+
{ $inc: { points: 50 } },
|
|
814
|
+
{ returnDocument: 'after' }
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
expect(result).toBeDefined();
|
|
818
|
+
expect(result.points).toBe(150);
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
test('should upsert if document does not exist', async () => {
|
|
822
|
+
if (skipIfMongoUnavailable()) return;
|
|
823
|
+
|
|
824
|
+
const result = await driver.findOneAndUpdate(
|
|
825
|
+
'users',
|
|
826
|
+
{ email: 'charlie@example.com' },
|
|
827
|
+
{ $set: { name: 'Charlie', email: 'charlie@example.com', status: 'active' } },
|
|
828
|
+
{ returnDocument: 'after', upsert: true }
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
expect(result).toBeDefined();
|
|
832
|
+
expect(result.email).toBe('charlie@example.com');
|
|
833
|
+
expect(result.name).toBe('Charlie');
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
test('should return null if no document matches', async () => {
|
|
837
|
+
if (skipIfMongoUnavailable()) return;
|
|
838
|
+
|
|
839
|
+
const result = await driver.findOneAndUpdate(
|
|
840
|
+
'users',
|
|
841
|
+
{ email: 'nonexistent@example.com' },
|
|
842
|
+
{ $set: { status: 'active' } },
|
|
843
|
+
{ returnDocument: 'after' }
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
expect(result).toBeNull();
|
|
847
|
+
});
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
describe('Change Streams (Watch)', () => {
|
|
851
|
+
test('should watch for insert operations', async () => {
|
|
852
|
+
if (skipIfMongoUnavailable()) return;
|
|
853
|
+
|
|
854
|
+
const changes: any[] = [];
|
|
855
|
+
|
|
856
|
+
// Start watching
|
|
857
|
+
const streamId = await driver.watch('users', async (change) => {
|
|
858
|
+
changes.push(change);
|
|
859
|
+
}, {
|
|
860
|
+
operationTypes: ['insert']
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
expect(streamId).toBeDefined();
|
|
864
|
+
|
|
865
|
+
// Give the change stream time to initialize
|
|
866
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
867
|
+
|
|
868
|
+
// Insert a document
|
|
869
|
+
await driver.create('users', { name: 'ChangeTest', email: 'test@example.com' });
|
|
870
|
+
|
|
871
|
+
// Wait for change to be detected
|
|
872
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
873
|
+
|
|
874
|
+
// Stop watching
|
|
875
|
+
await driver.unwatchChangeStream(streamId);
|
|
876
|
+
|
|
877
|
+
expect(changes.length).toBeGreaterThan(0);
|
|
878
|
+
expect(changes[0].operationType).toBe('insert');
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
test('should watch for update operations', async () => {
|
|
882
|
+
if (skipIfMongoUnavailable()) return;
|
|
883
|
+
|
|
884
|
+
// Create initial document
|
|
885
|
+
const doc = await driver.create('users', { name: 'UpdateTest', email: 'update@example.com' });
|
|
886
|
+
|
|
887
|
+
const changes: any[] = [];
|
|
888
|
+
|
|
889
|
+
// Start watching
|
|
890
|
+
const streamId = await driver.watch('users', async (change) => {
|
|
891
|
+
changes.push(change);
|
|
892
|
+
}, {
|
|
893
|
+
operationTypes: ['update'],
|
|
894
|
+
fullDocument: 'updateLookup'
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
expect(streamId).toBeDefined();
|
|
898
|
+
|
|
899
|
+
// Give the change stream time to initialize
|
|
900
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
901
|
+
|
|
902
|
+
// Update the document
|
|
903
|
+
await driver.update('users', doc.id, { name: 'UpdatedName' });
|
|
904
|
+
|
|
905
|
+
// Wait for change to be detected
|
|
906
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
907
|
+
|
|
908
|
+
// Stop watching
|
|
909
|
+
await driver.unwatchChangeStream(streamId);
|
|
910
|
+
|
|
911
|
+
expect(changes.length).toBeGreaterThan(0);
|
|
912
|
+
expect(changes[0].operationType).toBe('update');
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
test('should return list of active change streams', async () => {
|
|
916
|
+
if (skipIfMongoUnavailable()) return;
|
|
917
|
+
|
|
918
|
+
const streamId1 = await driver.watch('users', async () => {});
|
|
919
|
+
const streamId2 = await driver.watch('products', async () => {});
|
|
920
|
+
|
|
921
|
+
const activeStreams = driver.getActiveChangeStreams();
|
|
922
|
+
|
|
923
|
+
expect(activeStreams).toContain(streamId1);
|
|
924
|
+
expect(activeStreams).toContain(streamId2);
|
|
925
|
+
expect(activeStreams.length).toBe(2);
|
|
926
|
+
|
|
927
|
+
// Cleanup
|
|
928
|
+
await driver.unwatchChangeStream(streamId1);
|
|
929
|
+
await driver.unwatchChangeStream(streamId2);
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
test('should allow multiple watchers on same collection', async () => {
|
|
933
|
+
if (skipIfMongoUnavailable()) return;
|
|
934
|
+
|
|
935
|
+
const changes1: any[] = [];
|
|
936
|
+
const changes2: any[] = [];
|
|
937
|
+
|
|
938
|
+
const streamId1 = await driver.watch('users', async (change) => {
|
|
939
|
+
changes1.push(change);
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
const streamId2 = await driver.watch('users', async (change) => {
|
|
943
|
+
changes2.push(change);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
// Give streams time to initialize
|
|
947
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
948
|
+
|
|
949
|
+
// Create a document
|
|
950
|
+
await driver.create('users', { name: 'MultiWatch', email: 'multi@example.com' });
|
|
951
|
+
|
|
952
|
+
// Wait for changes to be detected
|
|
953
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
954
|
+
|
|
955
|
+
// Both watchers should have received the change
|
|
956
|
+
expect(changes1.length).toBeGreaterThan(0);
|
|
957
|
+
expect(changes2.length).toBeGreaterThan(0);
|
|
958
|
+
|
|
959
|
+
// Cleanup
|
|
960
|
+
await driver.unwatchChangeStream(streamId1);
|
|
961
|
+
await driver.unwatchChangeStream(streamId2);
|
|
962
|
+
});
|
|
963
|
+
});
|
|
719
964
|
});
|