@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.
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { MongoDriver } from '../src';
10
10
  import { MongoClient } from 'mongodb';
11
- import { MongoMemoryServer } from 'mongodb-memory-server';
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: MongoMemoryServer;
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. implementation in CI services)
42
- // Otherwise start an in-memory instance
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
- mongod = await MongoMemoryServer.create();
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
  });