package org.jooby.mongodb; import com.google.common.collect.ImmutableMap; import com.mongodb.DBObject; import com.mongodb.client.FindIterable; import com.mongodb.client.ListIndexesIterable; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; import com.mongodb.client.MongoDatabase; import com.mongodb.client.model.Filters; import com.mongodb.client.model.IndexOptions; import com.mongodb.client.model.UpdateOptions; import com.mongodb.client.result.UpdateResult; import org.bson.Document; import org.bson.conversions.Bson; import org.jooby.Session; import org.jooby.test.MockUnit; import org.jooby.test.MockUnit.Block; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import static org.easymock.EasyMock.expect; import static org.junit.Assert.assertEquals; @RunWith(PowerMockRunner.class) @PrepareForTest({MongoSessionStore.class, IndexOptions.class, UpdateOptions.class, Filters.class, LinkedHashMap.class}) public class MongodbSessionStoreTest { @SuppressWarnings({"unchecked", "rawtypes"}) MockUnit.Block boot = unit -> { MongoCollection collection = unit.get(MongoCollection.class); MongoDatabase db = unit.get(MongoDatabase.class); expect(db.getCollection("sess")).andReturn(collection); }; long now = System.currentTimeMillis(); Map<String, String> attrs = ImmutableMap.<String, String>of("k.v", "v", "$d", "d"); @SuppressWarnings("rawtypes") MockUnit.Block saveSession = unit -> { MongoCollection collection = unit.get(MongoCollection.class); Session session = unit.get(Session.class); expect(session.id()).andReturn("1234"); expect(session.accessedAt()).andReturn(now); expect(session.createdAt()).andReturn(now); expect(session.savedAt()).andReturn(now); expect(session.attributes()).andReturn(attrs); UpdateResult result = unit.mock(UpdateResult.class); Document doc = new Document() .append("_id", "1234") .append("_accessedAt", new Date(now)) .append("_createdAt", new Date(now)) .append("_savedAt", new Date(now)) .append("k\uFF0Ev", "v") .append("\uFF04d", "d"); UpdateOptions options = unit.constructor(UpdateOptions.class) .build(); expect(options.upsert(true)).andReturn(options); Bson eq = unit.mock(Bson.class); unit.mockStatic(Filters.class); expect(Filters.eq("_id", "1234")).andReturn(eq); expect(collection.updateOne(eq, new Document("$set", doc), options)) .andReturn(result); }; @SuppressWarnings("rawtypes") private Block noIndexes = unit -> { MongoCursor cursor = unit.mock(MongoCursor.class); expect(cursor.hasNext()).andReturn(false); ListIndexesIterable lii = unit.mock(ListIndexesIterable.class); expect(lii.iterator()).andReturn(cursor); MongoCollection coll = unit.get(MongoCollection.class); expect(coll.listIndexes()).andReturn(lii); }; @SuppressWarnings("rawtypes") private Block indexes = unit -> { Document d1 = unit.mock(Document.class); expect(d1.getString("name")).andReturn("n1"); Document d2 = unit.mock(Document.class); expect(d2.getString("name")).andReturn("_sessionIdx_"); MongoCursor cursor = unit.mock(MongoCursor.class); expect(cursor.hasNext()).andReturn(true); expect(cursor.next()).andReturn(d1); expect(cursor.hasNext()).andReturn(true); expect(cursor.next()).andReturn(d2); ListIndexesIterable lii = unit.mock(ListIndexesIterable.class); expect(lii.iterator()).andReturn(cursor); MongoCollection coll = unit.get(MongoCollection.class); expect(coll.listIndexes()).andReturn(lii); }; private Block runCommand = unit -> { MongoDatabase db = unit.get(MongoDatabase.class); Document command = new Document("collMod", "sess") .append("index", new Document("keyPattern", new Document("_accessedAt", 1)) .append("expireAfterSeconds", 60L)); expect(db.runCommand(command)).andReturn(unit.mock(Document.class)); }; @Test public void defaults() throws Exception { new MockUnit(MongoDatabase.class, MongoCollection.class) .expect(boot) .run(unit -> { new MongoSessionStore(unit.get(MongoDatabase.class), "sess", "1m"); }); } @Test(expected = NullPointerException.class) public void defaultsNullDB() throws Exception { new MongoSessionStore(null, "sess", "1m"); } @SuppressWarnings("rawtypes") @Test public void create() throws Exception { new MockUnit(Session.class, MongoDatabase.class, MongoCollection.class) .expect(boot) .expect(saveSession) .expect(noIndexes) .expect(unit -> { MongoCollection collection = unit.get(MongoCollection.class); IndexOptions options = unit.constructor(IndexOptions.class) .build(); expect(options.name("_sessionIdx_")).andReturn(options); expect(options.expireAfter(300L, TimeUnit.SECONDS)).andReturn(options); expect(collection.createIndex(new Document("_accessedAt", 1), options)) .andReturn("idx"); }) .run(unit -> { new MongoSessionStore(unit.get(MongoDatabase.class), "sess", "5m") .create(unit.get(Session.class)); ; }); } @Test public void save() throws Exception { new MockUnit(Session.class, MongoDatabase.class, MongoCollection.class) .expect(boot) .expect(indexes) .expect(saveSession) .expect(runCommand) .run(unit -> { new MongoSessionStore(unit.get(MongoDatabase.class), "sess", "60") .save(unit.get(Session.class)); ; }); } @SuppressWarnings("rawtypes") @Test public void shouldSyncTtlOnce() throws Exception { new MockUnit(Session.class, MongoDatabase.class, MongoCollection.class) .expect(boot) .expect(saveSession) .expect(noIndexes) .expect(saveSession) .expect(unit -> { MongoCollection collection = unit.get(MongoCollection.class); IndexOptions options = unit.constructor(IndexOptions.class) .build(); expect(options.name("_sessionIdx_")).andReturn(options); expect(options.expireAfter(60L, TimeUnit.SECONDS)).andReturn(options); expect(collection.createIndex(new Document("_accessedAt", 1), options)) .andReturn("idx"); }) .run(unit -> { MongoSessionStore mongodb = new MongoSessionStore(unit.get(MongoDatabase.class), "sess", "60"); mongodb.save(unit.get(Session.class)); mongodb.save(unit.get(Session.class)); }); } @Test public void saveNoTimeout() throws Exception { new MockUnit(Session.class, MongoDatabase.class, MongoCollection.class) .expect(boot) .expect(saveSession) .run(unit -> { new MongoSessionStore(unit.get(MongoDatabase.class), "sess", "0") .save(unit.get(Session.class)); ; }); } @Test public void saveSyncTtl() throws Exception { new MockUnit(Session.class, MongoDatabase.class, MongoCollection.class) .expect(boot) .expect(saveSession) .expect(indexes) .expect(runCommand) .run(unit -> { new MongoSessionStore(unit.get(MongoDatabase.class), "sess", "60") .save(unit.get(Session.class)); ; }); } @SuppressWarnings({"unchecked", "rawtypes"}) @Test public void get() throws Exception { long now = System.currentTimeMillis(); new MockUnit(Session.class, Session.Builder.class, MongoDatabase.class, MongoCollection.class, DBObject.class) .expect(boot) .expect(unit -> { Document doc = unit.mock(Document.class); Map sessionMap = unit.constructor(LinkedHashMap.class) .args(Map.class) .build(doc); expect(sessionMap.remove("_accessedAt")).andReturn(new Date(now)); expect(sessionMap.remove("_createdAt")).andReturn(new Date(now)); expect(sessionMap.remove("_savedAt")).andReturn(new Date(now)); expect(sessionMap.remove("_id")).andReturn("1234"); sessionMap.forEach(unit.capture(BiConsumer.class)); FindIterable result = unit.mock(FindIterable.class); expect(result.first()).andReturn(doc); Session.Builder sb = unit.get(Session.Builder.class); expect(sb.sessionId()).andReturn("1234"); expect(sb.accessedAt(now)).andReturn(sb); expect(sb.createdAt(now)).andReturn(sb); expect(sb.savedAt(now)).andReturn(sb); expect(sb.set("a.b", "c")).andReturn(sb); expect(sb.build()).andReturn(unit.get(Session.class)); Bson eq = unit.mock(Bson.class); unit.mockStatic(Filters.class); expect(Filters.eq("_id", "1234")).andReturn(eq); MongoCollection collection = unit.get(MongoCollection.class); expect(collection.find(eq)).andReturn(result); }) .run(unit -> { MongoSessionStore mss = new MongoSessionStore(unit.get(MongoDatabase.class), "sess", "60"); assertEquals(unit.get(Session.class), mss.get(unit.get(Session.Builder.class))); }, unit -> { BiConsumer<String, String> setter = unit.captured(BiConsumer.class).get(0); setter.accept("a\uFF0Eb", "c"); }); } @SuppressWarnings({"unchecked", "rawtypes"}) @Test public void getDollar() throws Exception { long now = System.currentTimeMillis(); new MockUnit(Session.class, Session.Builder.class, MongoDatabase.class, MongoCollection.class, DBObject.class) .expect(boot) .expect(unit -> { Document doc = unit.mock(Document.class); Map sessionMap = unit.constructor(LinkedHashMap.class) .args(Map.class) .build(doc); expect(sessionMap.remove("_accessedAt")).andReturn(new Date(now)); expect(sessionMap.remove("_createdAt")).andReturn(new Date(now)); expect(sessionMap.remove("_savedAt")).andReturn(new Date(now)); expect(sessionMap.remove("_id")).andReturn("1234"); sessionMap.forEach(unit.capture(BiConsumer.class)); FindIterable result = unit.mock(FindIterable.class); expect(result.first()).andReturn(doc); Session.Builder sb = unit.get(Session.Builder.class); expect(sb.sessionId()).andReturn("1234"); expect(sb.accessedAt(now)).andReturn(sb); expect(sb.createdAt(now)).andReturn(sb); expect(sb.savedAt(now)).andReturn(sb); expect(sb.set("$ab", "c")).andReturn(sb); expect(sb.build()).andReturn(unit.get(Session.class)); Bson eq = unit.mock(Bson.class); unit.mockStatic(Filters.class); expect(Filters.eq("_id", "1234")).andReturn(eq); MongoCollection collection = unit.get(MongoCollection.class); expect(collection.find(eq)).andReturn(result); }) .run(unit -> { MongoSessionStore mss = new MongoSessionStore(unit.get(MongoDatabase.class), "sess", "60"); assertEquals(unit.get(Session.class), mss.get(unit.get(Session.Builder.class))); }, unit -> { BiConsumer<String, String> setter = unit.captured(BiConsumer.class).get(0); setter.accept("\uFF04ab", "c"); }); } @SuppressWarnings("rawtypes") @Test public void getExpired() throws Exception { new MockUnit(Session.class, Session.Builder.class, MongoDatabase.class, MongoCollection.class) .expect(boot) .expect(unit -> { Session.Builder sb = unit.get(Session.Builder.class); expect(sb.sessionId()).andReturn("1234"); Bson eq = unit.mock(Bson.class); unit.mockStatic(Filters.class); expect(Filters.eq("_id", "1234")).andReturn(eq); FindIterable result = unit.mock(FindIterable.class); expect(result.first()).andReturn(null); MongoCollection collection = unit.get(MongoCollection.class); expect(collection.find(eq)).andReturn(result); }) .run(unit -> { assertEquals(null, new MongoSessionStore(unit.get(MongoDatabase.class), "sess", "60") .get(unit.get(Session.Builder.class))); }); } @SuppressWarnings("rawtypes") @Test public void delete() throws Exception { new MockUnit(MongoDatabase.class, MongoCollection.class) .expect(boot) .expect(unit -> { MongoCollection collection = unit.get(MongoCollection.class); expect(collection.deleteOne(new Document("_id", "1234"))).andReturn(null); }) .run(unit -> { new MongoSessionStore(unit.get(MongoDatabase.class), "sess", "60") .delete("1234"); }); } }