Skip to main content
Module

x/mongoose/test/query.test.js

MongoDB object modeling designed to work in an asynchronous environment.
Go to Latest
File
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310
'use strict';
/** * Module dependencies. */const start = require('./common');
const { EJSON } = require('bson');const Query = require('../lib/query');const assert = require('assert');const util = require('./util');
const mongoose = start.mongoose;const Schema = mongoose.Schema;const DocumentObjectId = mongoose.Types.ObjectId;
/** * Test. */describe('Query', function() { let commentSchema; let productSchema; let db; before(function() { commentSchema = new Schema({ text: String }); productSchema = new Schema({ tags: {}, // mixed array: Array, ids: [Schema.ObjectId], strings: [String], numbers: [Number], comments: [commentSchema] }); }); before(function() { db = start(); }); after(async function() { await db.close(); }); beforeEach(() => db.deleteModel(/.*/)); afterEach(() => util.clearTestData(db)); afterEach(() => require('./util').stopRemainingOps(db)); describe('constructor', function() { it('should not corrupt options', function(done) { const opts = {}; const query = new Query({}, opts); assert.notEqual(opts, query._mongooseOptions); done(); }); }); describe('select', function() { it('(object)', function(done) { const query = new Query({}); query.select({ a: 1, b: 1, c: 0 }); assert.deepEqual(query._fields, { a: 1, b: 1, c: 0 }); done(); }); it('(string)', function(done) { const query = new Query({}); query.select(' a b -c '); assert.deepEqual(query._fields, { a: 1, b: 1, c: 0 }); done(); }); it('("a","b","c")', function(done) { assert.throws(function() { const query = new Query({}); query.select('a', 'b', 'c'); }, /Invalid select/); done(); }); it('should not overwrite fields set in prior calls', function(done) { const query = new Query({}); query.select('a'); assert.deepEqual(query._fields, { a: 1 }); query.select('b'); assert.deepEqual(query._fields, { a: 1, b: 1 }); query.select({ c: 1 }); assert.deepEqual(query._fields, { a: 1, b: 1, c: 1 }); query.select('d'); assert.deepEqual(query._fields, { a: 1, b: 1, c: 1, d: 1 }); done(); }); it('should remove existing fields from inclusive projection', function(done) { const query = new Query({}); query.select({ a: 1, b: 1, c: 1, 'parent1.child1': 1, 'parent1.child2': 1, 'parent2.child1': 1, 'parent2.child2': 1 }).select({ b: 0, d: 1, 'c.child': 0, parent1: 0, 'parent2.child1': 0 }); assert.deepEqual(query._fields, { a: 1, c: 1, d: 1, 'parent2.child2': 1 }); done(); }); it('should remove existing fields from exclusive projection', function(done) { const query = new Query({}); query.select({ a: 0, b: 0, c: 0, 'parent1.child1': 0, 'parent1.child2': 0, 'parent2.child1': 0, 'parent2.child2': 0 }).select({ b: 1, d: 0, 'c.child': 1, parent1: 1, 'parent2.child1': 1 }); assert.deepEqual(query._fields, { a: 0, c: 0, d: 0, 'parent2.child2': 0 }); done(); }); }); describe('projection() (gh-7384)', function() { it('gets current projection', function() { const query = new Query({}); query.select('a'); assert.deepEqual(query.projection(), { a: 1 }); }); it('overwrites current projection', function() { const query = new Query({}); query.select('a'); assert.deepEqual(query.projection({ b: 1 }), { b: 1 }); assert.deepEqual(query.projection(), { b: 1 }); }); }); describe('where', function() { it('works', function(done) { const query = new Query({}); query.where('name', 'guillermo'); assert.deepEqual(query._conditions, { name: 'guillermo' }); query.where('a'); query.equals('b'); assert.deepEqual(query._conditions, { name: 'guillermo', a: 'b' }); done(); }); it('throws if non-string or non-object path is passed', function(done) { const query = new Query({}); assert.throws(function() { query.where(50); }); assert.throws(function() { query.where([]); }); done(); }); it('does not throw when 0 args passed', function(done) { const query = new Query({}); assert.doesNotThrow(function() { query.where(); }); done(); }); }); describe('equals', function() { it('works', function(done) { const query = new Query({}); query.where('name').equals('guillermo'); assert.deepEqual(query._conditions, { name: 'guillermo' }); done(); }); }); describe('gte', function() { it('with 2 args', function(done) { const query = new Query({}); query.gte('age', 18); assert.deepEqual(query._conditions, { age: { $gte: 18 } }); done(); }); it('with 1 arg', function(done) { const query = new Query({}); query.where('age').gte(18); assert.deepEqual(query._conditions, { age: { $gte: 18 } }); done(); }); }); describe('gt', function() { it('with 1 arg', function(done) { const query = new Query({}); query.where('age').gt(17); assert.deepEqual(query._conditions, { age: { $gt: 17 } }); done(); }); it('with 2 args', function(done) { const query = new Query({}); query.gt('age', 17); assert.deepEqual(query._conditions, { age: { $gt: 17 } }); done(); }); }); describe('lte', function() { it('with 1 arg', function(done) { const query = new Query({}); query.where('age').lte(65); assert.deepEqual(query._conditions, { age: { $lte: 65 } }); done(); }); it('with 2 args', function(done) { const query = new Query({}); query.lte('age', 65); assert.deepEqual(query._conditions, { age: { $lte: 65 } }); done(); }); }); describe('lt', function() { it('with 1 arg', function(done) { const query = new Query({}); query.where('age').lt(66); assert.deepEqual(query._conditions, { age: { $lt: 66 } }); done(); }); it('with 2 args', function(done) { const query = new Query({}); query.lt('age', 66); assert.deepEqual(query._conditions, { age: { $lt: 66 } }); done(); }); }); describe('combined', function() { describe('lt and gt', function() { it('works', function(done) { const query = new Query({}); query.where('age').lt(66).gt(17); assert.deepEqual(query._conditions, { age: { $lt: 66, $gt: 17 } }); done(); }); }); }); describe('tl on one path and gt on another', function() { it('works', function(done) { const query = new Query({}); query .where('age').lt(66) .where('height').gt(5); assert.deepEqual(query._conditions, { age: { $lt: 66 }, height: { $gt: 5 } }); done(); }); }); describe('ne', function() { it('with 1 arg', function(done) { const query = new Query({}); query.where('age').ne(21); assert.deepEqual(query._conditions, { age: { $ne: 21 } }); done(); }); it('with 2 args', function(done) { const query = new Query({}); query.ne('age', 21); assert.deepEqual(query._conditions, { age: { $ne: 21 } }); done(); }); }); describe('in', function() { it('with 1 arg', function(done) { const query = new Query({}); query.where('age').in([21, 25, 30]); assert.deepEqual(query._conditions, { age: { $in: [21, 25, 30] } }); done(); }); it('with 2 args', function(done) { const query = new Query({}); query.in('age', [21, 25, 30]); assert.deepEqual(query._conditions, { age: { $in: [21, 25, 30] } }); done(); }); it('where a non-array value no via where', function(done) { const query = new Query({}); query.in('age', 21); assert.deepEqual(query._conditions, { age: { $in: 21 } }); done(); }); it('where a non-array value via where', function(done) { const query = new Query({}); query.where('age').in(21); assert.deepEqual(query._conditions, { age: { $in: 21 } }); done(); }); }); describe('nin', function() { it('with 1 arg', function(done) { const query = new Query({}); query.where('age').nin([21, 25, 30]); assert.deepEqual(query._conditions, { age: { $nin: [21, 25, 30] } }); done(); }); it('with 2 args', function(done) { const query = new Query({}); query.nin('age', [21, 25, 30]); assert.deepEqual(query._conditions, { age: { $nin: [21, 25, 30] } }); done(); }); it('with a non-array value not via where', function(done) { const query = new Query({}); query.nin('age', 21); assert.deepEqual(query._conditions, { age: { $nin: 21 } }); done(); }); it('with a non-array value via where', function(done) { const query = new Query({}); query.where('age').nin(21); assert.deepEqual(query._conditions, { age: { $nin: 21 } }); done(); }); }); describe('mod', function() { it('not via where, where [a, b] param', function(done) { const query = new Query({}); query.mod('age', [5, 2]); assert.deepEqual(query._conditions, { age: { $mod: [5, 2] } }); done(); }); it('not via where, where a and b params', function(done) { const query = new Query({}); query.mod('age', 5, 2); assert.deepEqual(query._conditions, { age: { $mod: [5, 2] } }); done(); }); it('via where, where [a, b] param', function(done) { const query = new Query({}); query.where('age').mod([5, 2]); assert.deepEqual(query._conditions, { age: { $mod: [5, 2] } }); done(); }); it('via where, where a and b params', function(done) { const query = new Query({}); query.where('age').mod(5, 2); assert.deepEqual(query._conditions, { age: { $mod: [5, 2] } }); done(); }); }); describe('near', function() { it('via where, where { center :[lat, long]} param', function(done) { const query = new Query({}); query.where('checkin').near({ center: [40, -72] }); assert.deepEqual(query._conditions, { checkin: { $near: [40, -72] } }); done(); }); it('via where, where [lat, long] param', function(done) { const query = new Query({}); query.where('checkin').near([40, -72]); assert.deepEqual(query._conditions, { checkin: { $near: [40, -72] } }); done(); }); it('via where, where lat and long params', function(done) { const query = new Query({}); query.where('checkin').near(40, -72); assert.deepEqual(query._conditions, { checkin: { $near: [40, -72] } }); done(); }); it('not via where, where [lat, long] param', function(done) { const query = new Query({}); query.near('checkin', [40, -72]); assert.deepEqual(query._conditions, { checkin: { $near: [40, -72] } }); done(); }); it('not via where, where lat and long params', function(done) { const query = new Query({}); query.near('checkin', 40, -72); assert.deepEqual(query._conditions, { checkin: { $near: [40, -72] } }); done(); }); it('via where, where GeoJSON param', function(done) { const query = new Query({}); query.where('numbers').near({ center: { type: 'Point', coordinates: [40, -72] } }); assert.deepEqual(query._conditions, { numbers: { $near: { $geometry: { type: 'Point', coordinates: [40, -72] } } } }); assert.doesNotThrow(function() { query.cast(db.model('Product', productSchema)); }); done(); }); it('with path, where GeoJSON param', function(done) { const query = new Query({}); query.near('loc', { center: { type: 'Point', coordinates: [40, -72] } }); assert.deepEqual(query._conditions, { loc: { $near: { $geometry: { type: 'Point', coordinates: [40, -72] } } } }); done(); }); }); describe('nearSphere', function() { it('via where, where [lat, long] param', function(done) { const query = new Query({}); query.where('checkin').nearSphere([40, -72]); assert.deepEqual(query._conditions, { checkin: { $nearSphere: [40, -72] } }); done(); }); it('via where, where lat and long params', function(done) { const query = new Query({}); query.where('checkin').nearSphere(40, -72); assert.deepEqual(query._conditions, { checkin: { $nearSphere: [40, -72] } }); done(); }); it('not via where, where [lat, long] param', function(done) { const query = new Query({}); query.nearSphere('checkin', [40, -72]); assert.deepEqual(query._conditions, { checkin: { $nearSphere: [40, -72] } }); done(); }); it('not via where, where lat and long params', function(done) { const query = new Query({}); query.nearSphere('checkin', 40, -72); assert.deepEqual(query._conditions, { checkin: { $nearSphere: [40, -72] } }); done(); }); it('via where, with object', function(done) { const query = new Query({}); query.where('checkin').nearSphere({ center: [20, 23], maxDistance: 2 }); assert.deepEqual(query._conditions, { checkin: { $nearSphere: [20, 23], $maxDistance: 2 } }); done(); }); it('via where, where GeoJSON param', function(done) { const query = new Query({}); query.where('numbers').nearSphere({ center: { type: 'Point', coordinates: [40, -72] } }); assert.deepEqual(query._conditions, { numbers: { $nearSphere: { $geometry: { type: 'Point', coordinates: [40, -72] } } } }); assert.doesNotThrow(function() { query.cast(db.model('Product', productSchema)); }); done(); }); it('with path, with GeoJSON', function(done) { const query = new Query({}); query.nearSphere('numbers', { center: { type: 'Point', coordinates: [40, -72] } }); assert.deepEqual(query._conditions, { numbers: { $nearSphere: { $geometry: { type: 'Point', coordinates: [40, -72] } } } }); assert.doesNotThrow(function() { query.cast(db.model('Product', productSchema)); }); done(); }); }); describe('maxDistance', function() { it('via where', function(done) { const query = new Query({}); query.where('checkin').near([40, -72]).maxDistance(1); assert.deepEqual(query._conditions, { checkin: { $near: [40, -72], $maxDistance: 1 } }); done(); }); }); describe('within', function() { describe('box', function() { it('via where', function(done) { const query = new Query({}); query.where('gps').within().box({ ll: [5, 25], ur: [10, 30] }); const match = { gps: { $within: { $box: [[5, 25], [10, 30]] } } }; if (Query.use$geoWithin) { match.gps.$geoWithin = match.gps.$within; delete match.gps.$within; } assert.deepEqual(query._conditions, match); done(); }); it('via where, no object', function(done) { const query = new Query({}); query.where('gps').within().box([5, 25], [10, 30]); const match = { gps: { $within: { $box: [[5, 25], [10, 30]] } } }; if (Query.use$geoWithin) { match.gps.$geoWithin = match.gps.$within; delete match.gps.$within; } assert.deepEqual(query._conditions, match); done(); }); }); describe('center', function() { it('via where', function(done) { const query = new Query({}); query.where('gps').within().center({ center: [5, 25], radius: 5 }); const match = { gps: { $within: { $center: [[5, 25], 5] } } }; if (Query.use$geoWithin) { match.gps.$geoWithin = match.gps.$within; delete match.gps.$within; } assert.deepEqual(query._conditions, match); done(); }); }); describe('centerSphere', function() { it('via where', function(done) { const query = new Query({}); query.where('gps').within().centerSphere({ center: [5, 25], radius: 5 }); const match = { gps: { $within: { $centerSphere: [[5, 25], 5] } } }; if (Query.use$geoWithin) { match.gps.$geoWithin = match.gps.$within; delete match.gps.$within; } assert.deepEqual(query._conditions, match); done(); }); }); describe('polygon', function() { it('via where', function(done) { const query = new Query({}); query.where('gps').within().polygon({ a: { x: 10, y: 20 }, b: { x: 15, y: 25 }, c: { x: 20, y: 20 } }); const match = { gps: { $within: { $polygon: [{ a: { x: 10, y: 20 }, b: { x: 15, y: 25 }, c: { x: 20, y: 20 } }] } } }; if (Query.use$geoWithin) { match.gps.$geoWithin = match.gps.$within; delete match.gps.$within; } assert.deepEqual(query._conditions, match); done(); }); }); }); describe('exists', function() { it('0 args via where', function(done) { const query = new Query({}); query.where('username').exists(); assert.deepEqual(query._conditions, { username: { $exists: true } }); done(); }); it('1 arg via where', function(done) { const query = new Query({}); query.where('username').exists(false); assert.deepEqual(query._conditions, { username: { $exists: false } }); done(); }); it('where 1 argument not via where', function(done) { const query = new Query({}); query.exists('username'); assert.deepEqual(query._conditions, { username: { $exists: true } }); done(); }); it('where 2 args not via where', function(done) { const query = new Query({}); query.exists('username', false); assert.deepEqual(query._conditions, { username: { $exists: false } }); done(); }); }); describe('all', function() { it('via where', function(done) { const query = new Query({}); query.where('pets').all(['dog', 'cat', 'ferret']); assert.deepEqual(query._conditions, { pets: { $all: ['dog', 'cat', 'ferret'] } }); done(); }); it('not via where', function(done) { const query = new Query({}); query.all('pets', ['dog', 'cat', 'ferret']); assert.deepEqual(query._conditions, { pets: { $all: ['dog', 'cat', 'ferret'] } }); done(); }); }); describe('find', function() { it('strict array equivalence condition v', function(done) { const query = new Query({}); query.find({ pets: ['dog', 'cat', 'ferret'] }); assert.deepEqual(query._conditions, { pets: ['dog', 'cat', 'ferret'] }); done(); }); it('with no args', function(done) { let threw = false; const q = new Query({}); try { q.find(); } catch (err) { threw = true; } assert.ok(!threw); done(); }); it('works with overwriting previous object args (1176)', function(done) { const q = new Query({}); assert.doesNotThrow(function() { q.find({ age: { $lt: 30 } }); q.find({ age: 20 }); // overwrite }); assert.deepEqual({ age: 20 }, q._conditions); done(); }); }); describe('size', function() { it('via where', function(done) { const query = new Query({}); query.where('collection').size(5); assert.deepEqual(query._conditions, { collection: { $size: 5 } }); done(); }); it('not via where', function(done) { const query = new Query({}); query.size('collection', 5); assert.deepEqual(query._conditions, { collection: { $size: 5 } }); done(); }); }); describe('slice', function() { it('where and positive limit param', function(done) { const query = new Query({}); query.where('collection').slice(5); assert.deepEqual(query._fields, { collection: { $slice: 5 } }); done(); }); it('where just negative limit param', function(done) { const query = new Query({}); query.where('collection').slice(-5); assert.deepEqual(query._fields, { collection: { $slice: -5 } }); done(); }); it('where [skip, limit] param', function(done) { const query = new Query({}); query.where('collection').slice([14, 10]); // Return the 15th through 25th assert.deepEqual(query._fields, { collection: { $slice: [14, 10] } }); done(); }); it('where skip and limit params', function(done) { const query = new Query({}); query.where('collection').slice(14, 10); // Return the 15th through 25th assert.deepEqual(query._fields, { collection: { $slice: [14, 10] } }); done(); }); it('where just positive limit param', function(done) { const query = new Query({}); query.where('collection').slice(5); assert.deepEqual(query._fields, { collection: { $slice: 5 } }); done(); }); it('where just negative limit param', function(done) { const query = new Query({}); query.where('collection').slice(-5); assert.deepEqual(query._fields, { collection: { $slice: -5 } }); done(); }); it('where the [skip, limit] param', function(done) { const query = new Query({}); query.where('collection').slice([14, 10]); // Return the 15th through 25th assert.deepEqual(query._fields, { collection: { $slice: [14, 10] } }); done(); }); it('where the skip and limit params', function(done) { const query = new Query({}); query.where('collection').slice(14, 10); // Return the 15th through 25th assert.deepEqual(query._fields, { collection: { $slice: [14, 10] } }); done(); }); it('not via where, with just positive limit param', function(done) { const query = new Query({}); query.slice('collection', 5); assert.deepEqual(query._fields, { collection: { $slice: 5 } }); done(); }); it('not via where, where just negative limit param', function(done) { const query = new Query({}); query.slice('collection', -5); assert.deepEqual(query._fields, { collection: { $slice: -5 } }); done(); }); it('not via where, where [skip, limit] param', function(done) { const query = new Query({}); query.slice('collection', [14, 10]); // Return the 15th through 25th assert.deepEqual(query._fields, { collection: { $slice: [14, 10] } }); done(); }); it('not via where, where skip and limit params', function(done) { const query = new Query({}); query.slice('collection', 14, 10); // Return the 15th through 25th assert.deepEqual(query._fields, { collection: { $slice: [14, 10] } }); done(); }); }); describe('elemMatch', function() { describe('not via where', function() { it('works', function(done) { const query = new Query({}); query.elemMatch('comments', { author: 'bnoguchi', votes: { $gte: 5 } }); assert.deepEqual(query._conditions, { comments: { $elemMatch: { author: 'bnoguchi', votes: { $gte: 5 } } } }); done(); }); it('where block notation', function(done) { const query = new Query({}); query.elemMatch('comments', function(elem) { elem.where('author', 'bnoguchi'); elem.where('votes').gte(5); }); assert.deepEqual(query._conditions, { comments: { $elemMatch: { author: 'bnoguchi', votes: { $gte: 5 } } } }); done(); }); }); describe('via where', function() { it('works', function(done) { const query = new Query({}); query.where('comments').elemMatch({ author: 'bnoguchi', votes: { $gte: 5 } }); assert.deepEqual(query._conditions, { comments: { $elemMatch: { author: 'bnoguchi', votes: { $gte: 5 } } } }); done(); }); it('where block notation', function(done) { const query = new Query({}); query.where('comments').elemMatch(function(elem) { elem.where('author', 'bnoguchi'); elem.where('votes').gte(5); }); assert.deepEqual(query._conditions, { comments: { $elemMatch: { author: 'bnoguchi', votes: { $gte: 5 } } } }); done(); }); }); }); describe('$where', function() { it('function arg', function(done) { const query = new Query({}); function filter() { return this.lastName === this.firstName; } query.$where(filter); assert.deepEqual(query._conditions, { $where: filter }); done(); }); it('string arg', function(done) { const query = new Query({}); query.$where('this.lastName === this.firstName'); assert.deepEqual(query._conditions, { $where: 'this.lastName === this.firstName' }); done(); }); }); describe('limit', function() { it('works', function(done) { const query = new Query({}); query.limit(5); assert.strictEqual(query.options.limit, 5); done(); }); it('with string limit (gh-11017)', function() { const query = new Query({}); query.limit('5'); assert.strictEqual(query.options.limit, 5); assert.throws(() => query.limit('fail'), /CastError/); }); }); describe('skip', function() { it('works', function(done) { const query = new Query({}); query.skip(9); assert.equal(query.options.skip, 9); done(); }); }); describe('sort', function() { it('works', function(done) { let query = new Query({}); query.sort('a -c b'); assert.deepEqual(query.options.sort, { a: 1, c: -1, b: 1 }); query = new Query({}); query.sort({ a: 1, c: -1, b: 'asc', e: 'descending', f: 'ascending' }); assert.deepEqual(query.options.sort, { a: 1, c: -1, b: 1, e: -1, f: 1 }); if (typeof global.Map !== 'undefined') { query = new Query({}); query.sort(new global.Map().set('a', 1).set('b', 1)); assert.equal(query.options.sort.get('a'), 1); assert.equal(query.options.sort.get('b'), 1); } query = new Query({}); let e; try { query.sort(['a', 1]); } catch (err) { e = err; } assert.ok(e, 'uh oh. no error was thrown'); assert.equal(e.message, 'Invalid sort() argument, must be array of arrays'); e = undefined; try { query.sort('a', 1, 'c', -1, 'b', 1); } catch (err) { e = err; } assert.ok(e, 'uh oh. no error was thrown'); assert.equal(e.message, 'sort() only takes 1 Argument'); done(); }); }); describe('or', function() { it('works', function(done) { const query = new Query(); query.find({ $or: [{ x: 1 }, { x: 2 }] }); assert.equal(query._conditions.$or.length, 2); query.or([{ y: 'We\'re under attack' }, { z: 47 }]); assert.equal(query._conditions.$or.length, 4); assert.equal(query._conditions.$or[3].z, 47); query.or({ z: 'phew' }); assert.equal(query._conditions.$or.length, 5); assert.equal(query._conditions.$or[3].z, 47); assert.equal(query._conditions.$or[4].z, 'phew'); done(); }); }); describe('and', function() { it('works', function(done) { const query = new Query(); query.find({ $and: [{ x: 1 }, { y: 2 }] }); assert.equal(query._conditions.$and.length, 2); query.and([{ z: 'We\'re under attack' }, { w: 47 }]); assert.equal(query._conditions.$and.length, 4); assert.equal(query._conditions.$and[3].w, 47); query.and({ a: 'phew' }); assert.equal(query._conditions.$and.length, 5); assert.equal(query._conditions.$and[0].x, 1); assert.equal(query._conditions.$and[1].y, 2); assert.equal(query._conditions.$and[2].z, 'We\'re under attack'); assert.equal(query._conditions.$and[3].w, 47); assert.equal(query._conditions.$and[4].a, 'phew'); done(); }); }); describe('populate', function() { it('converts to PopulateOptions objects', function(done) { const q = new Query({}); const o = { path: 'yellow.brick', match: { bricks: { $lt: 1000 } }, select: undefined, model: undefined, options: undefined, _docs: {}, _childDocs: [] }; q.populate(o); assert.deepEqual(o, q._mongooseOptions.populate['yellow.brick']); done(); }); it('overwrites duplicate paths', function(done) { const q = new Query({}); let o = { path: 'yellow.brick', match: { bricks: { $lt: 1000 } }, _docs: {}, _childDocs: [] }; q.populate(Object.assign({}, o)); assert.equal(Object.keys(q._mongooseOptions.populate).length, 1); assert.deepEqual(q._mongooseOptions.populate['yellow.brick'], o); q.populate('yellow.brick'); o = { path: 'yellow.brick', _docs: {}, _childDocs: [] }; assert.equal(Object.keys(q._mongooseOptions.populate).length, 1); assert.deepEqual(q._mongooseOptions.populate['yellow.brick'], o); done(); }); it('accepts space delimited strings', function(done) { const q = new Query({}); q.populate('yellow.brick dirt'); assert.equal(Object.keys(q._mongooseOptions.populate).length, 2); assert.deepEqual(q._mongooseOptions.populate['yellow.brick'], { path: 'yellow.brick', _docs: {}, _childDocs: [] }); assert.deepEqual(q._mongooseOptions.populate['dirt'], { path: 'dirt', _docs: {}, _childDocs: [] }); done(); }); }); describe('casting', function() { it('to an array of mixed', function(done) { const query = new Query({}); const Product = db.model('Product', productSchema); const params = { _id: new DocumentObjectId(), tags: { $in: [4, 8, 15, 16] } }; query.cast(Product, params); assert.deepEqual(params.tags.$in, [4, 8, 15, 16]); done(); }); it('doesn\'t wipe out $in (gh-6439)', async function() { const embeddedSchema = new Schema({ name: String }, { _id: false }); const catSchema = new Schema({ name: String, props: [embeddedSchema] }); const Cat = db.model('Cat', catSchema); const kitty = new Cat({ name: 'Zildjian', props: [ { name: 'invalid' }, { name: 'abc' }, { name: 'def' } ] });
await kitty.save(); const cond = { _id: kitty._id }; const update = { $pull: { props: { $in: [ { name: 'invalid' }, { name: 'def' } ] } } }; await Cat.updateOne(cond, update); const found = await Cat.findOne(cond); assert.strictEqual(found.props[0].name, 'abc'); }); it('find $ne should not cast single value to array for schematype of Array', function(done) { const query = new Query({}); const Product = db.model('Product', productSchema); const Comment = db.model('Comment', commentSchema); const id = new DocumentObjectId(); const castedComment = { _id: id, text: 'hello there' }; const comment = new Comment(castedComment); const params = { array: { $ne: 5 }, ids: { $ne: id }, comments: { $ne: comment }, strings: { $ne: 'Hi there' }, numbers: { $ne: 10000 } }; query.cast(Product, params); assert.equal(params.array.$ne, 5); assert.equal(params.ids.$ne, id); params.comments.$ne._id.toHexString(); assert.deepEqual(params.comments.$ne.toObject(), castedComment); assert.equal(params.strings.$ne, 'Hi there'); assert.equal(params.numbers.$ne, 10000); params.array.$ne = [5]; params.ids.$ne = [id]; params.comments.$ne = [comment]; params.strings.$ne = ['Hi there']; params.numbers.$ne = [10000]; query.cast(Product, params); assert.ok(params.array.$ne instanceof Array); assert.equal(params.array.$ne[0], 5); assert.ok(params.ids.$ne instanceof Array); assert.equal(params.ids.$ne[0].toString(), id.toString()); assert.ok(params.comments.$ne instanceof Array); assert.deepEqual(params.comments.$ne[0].toObject(), castedComment); assert.ok(params.strings.$ne instanceof Array); assert.equal(params.strings.$ne[0], 'Hi there'); assert.ok(params.numbers.$ne instanceof Array); assert.equal(params.numbers.$ne[0], 10000); done(); }); it('subdocument array with $ne: null should not throw', function(done) { const query = new Query({}); const Product = db.model('Product', productSchema); const params = { comments: { $ne: null } }; query.cast(Product, params); assert.strictEqual(params.comments.$ne, null); done(); }); it('find should not cast single value to array for schematype of Array', function(done) { const query = new Query({}); const Product = db.model('Product', productSchema); const Comment = db.model('Comment', commentSchema); const id = new DocumentObjectId(); const castedComment = { _id: id, text: 'hello there' }; const comment = new Comment(castedComment); const params = { array: 5, ids: id, comments: comment, strings: 'Hi there', numbers: 10000 }; query.cast(Product, params); assert.equal(params.array, 5); assert.equal(params.ids, id); params.comments._id.toHexString(); assert.deepEqual(params.comments.toObject(), castedComment); assert.equal(params.strings, 'Hi there'); assert.equal(params.numbers, 10000); params.array = [5]; params.ids = [id]; params.comments = [comment]; params.strings = ['Hi there']; params.numbers = [10000]; query.cast(Product, params); assert.ok(params.array instanceof Array); assert.equal(params.array[0], 5); assert.ok(params.ids instanceof Array); assert.equal(params.ids[0].toString(), id.toString()); assert.ok(params.comments instanceof Array); assert.deepEqual(params.comments[0].toObject(), castedComment); assert.ok(params.strings instanceof Array); assert.equal(params.strings[0], 'Hi there'); assert.ok(params.numbers instanceof Array); assert.equal(params.numbers[0], 10000); done(); }); it('an $elemMatch with $in works (gh-1100)', function(done) { const query = new Query({}); const Product = db.model('Product', productSchema); const ids = [String(new DocumentObjectId()), String(new DocumentObjectId())]; const params = { ids: { $elemMatch: { $in: ids } } }; query.cast(Product, params); assert.ok(params.ids.$elemMatch.$in[0] instanceof DocumentObjectId); assert.ok(params.ids.$elemMatch.$in[1] instanceof DocumentObjectId); assert.deepEqual(params.ids.$elemMatch.$in[0].toString(), ids[0]); assert.deepEqual(params.ids.$elemMatch.$in[1].toString(), ids[1]); done(); }); it('inequality operators for an array', function(done) { const query = new Query({}); const Product = db.model('Product', productSchema); const Comment = db.model('Comment', commentSchema); const id = new DocumentObjectId(); const castedComment = { _id: id, text: 'hello there' }; const comment = new Comment(castedComment); const params = { ids: { $gt: id }, comments: { $gt: comment }, strings: { $gt: 'Hi there' }, numbers: { $gt: 10000 } }; query.cast(Product, params); assert.equal(params.ids.$gt, id); assert.deepEqual(params.comments.$gt.toObject(), castedComment); assert.equal(params.strings.$gt, 'Hi there'); assert.equal(params.numbers.$gt, 10000); done(); }); }); describe('distinct', function() { it('op', function() { const q = new Query({}); q.distinct('blah'); assert.equal(q.op, 'distinct'); }); }); describe('findOne', function() { it('sets the op', function(done) { const Product = db.model('Product', productSchema); const prod = new Product({}); const q = new Query(prod.collection, {}, Product).distinct(); // use a timeout here because we have to wait for the connection to start // before any ops will get set setTimeout(function() { assert.equal(q.op, 'distinct'); q.findOne(); assert.equal(q.op, 'findOne'); done(); }, 50); }); it('works as a promise', function(done) { const Product = db.model('Product', productSchema); const promise = Product.findOne(); promise.then(function() { done(); }, function(err) { assert.ifError(err); }); }); }); describe('deleteOne/deleteMany', function() { it('handles deleteOne', async function() { const M = db.model('Person', new Schema({ name: 'String' }));
await M.deleteMany({}); await M.create([{ name: 'Eddard Stark' }, { name: 'Robb Stark' }]); await M.deleteOne({ name: /Stark/ }); const count = await M.countDocuments(); assert.equal(count, 1); }); it('handles deleteMany', async function() { const M = db.model('Person', new Schema({ name: 'String' }));
await M.deleteMany({}); await M.create([{ name: 'Eddard Stark' }, { name: 'Robb Stark' }]); await M.deleteMany({ name: /Stark/ }); const count = await M.countDocuments(); assert.equal(count, 0); }); }); describe('remove', function() { it('handles cast errors async', function(done) { const Product = db.model('Product', productSchema); assert.doesNotThrow(function() { Product.where({ numbers: [[[]]] }).deleteMany(function(err) { assert.ok(err); done(); }); }); }); it('supports a single conditions arg', function(done) { const Product = db.model('Product', productSchema); Product.create({ strings: ['remove-single-condition'] }).then(function() { const q = Product.where().deleteMany({ strings: 'remove-single-condition' }); assert.ok(q instanceof mongoose.Query); done(); }, done); }); it('supports a single callback arg', function(done) { const Product = db.model('Product', productSchema); const val = 'remove-single-callback'; Product.create({ strings: [val] }).then(function() { Product.where({ strings: val }).deleteMany(function(err) { assert.ifError(err); Product.findOne({ strings: val }, function(err, doc) { assert.ifError(err); assert.ok(!doc); done(); }); }); }, done); }); it('supports conditions and callback args', function(done) { const Product = db.model('Product', productSchema); const val = 'remove-cond-and-callback'; Product.create({ strings: [val] }).then(function() { Product.where().deleteMany({ strings: val }, function(err) { assert.ifError(err); Product.findOne({ strings: val }, function(err, doc) { assert.ifError(err); assert.ok(!doc); done(); }); }); }, done); }); }); describe('querying/updating with model instance containing embedded docs should work (#454)', function() { it('works', function(done) { const Product = db.model('Product', productSchema); const proddoc = { comments: [{ text: 'hello' }] }; const prod2doc = { comments: [{ text: 'goodbye' }] }; const prod = new Product(proddoc); prod.save(function(err) { assert.ifError(err); Product.findOne({ _id: prod._id }, function(err, product) { assert.ifError(err); assert.equal(product.comments.length, 1); assert.equal(product.comments[0].text, 'hello'); Product.updateOne({ _id: prod._id }, prod2doc, function(err) { assert.ifError(err); Product.collection.findOne({ _id: product._id }, function(err, doc) { assert.ifError(err); assert.equal(doc.comments.length, 1); // ensure hidden private props were not saved to db assert.ok(!doc.comments[0].hasOwnProperty('parentArry')); assert.equal(doc.comments[0].text, 'goodbye'); done(); }); }); }); }); }); }); describe('optionsForExec', function() { it('should retain key order', function(done) { // this is important for query hints const hint = { x: 1, y: 1, z: 1 }; const a = JSON.stringify({ hint: hint }); const q = new Query(); q.hint(hint); const options = q._optionsForExec({ schema: { options: {} } }); assert.equal(JSON.stringify(options), a); done(); }); it('applies schema-level writeConcern option', function(done) { const q = new Query(); q.j(true); const options = q._optionsForExec({ schema: { options: { writeConcern: { w: 'majority' } } } }); assert.deepEqual(options, { writeConcern: { w: 'majority', j: true } }); done(); }); it('session() (gh-6663)', function(done) { const q = new Query(); const fakeSession = 'foo'; q.session(fakeSession); const options = q._optionsForExec(); assert.deepEqual(options, { session: fakeSession }); done(); }); }); // Advanced Query options describe('options', function() { describe('maxscan', function() { it('works', function(done) { const query = new Query({}); query.maxscan(100); assert.equal(query.options.maxScan, 100); done(); }); }); describe('slaveOk', function() { it('works', function(done) { let query = new Query({}); query.slaveOk(); assert.equal(query.options.slaveOk, true); query = new Query({}); query.slaveOk(true); assert.equal(query.options.slaveOk, true); query = new Query({}); query.slaveOk(false); assert.equal(query.options.slaveOk, false); done(); }); }); describe('tailable', function() { it('works', function(done) { let query = new Query({}); query.tailable(); assert.equal(query.options.tailable, true); query = new Query({}); query.tailable(true); assert.equal(query.options.tailable, true); query = new Query({}); query.tailable(false); assert.equal(query.options.tailable, false); done(); }); it('supports passing the `awaitData` option', function(done) { const query = new Query({}); query.tailable({ awaitData: true }); assert.equal(query.options.tailable, true); assert.equal(query.options.awaitData, true); done(); }); }); describe('comment', function() { it('works', function(done) { const query = new Query(); assert.equal(typeof query.comment, 'function'); assert.equal(query.comment('Lowpass is more fun'), query); assert.equal(query.options.comment, 'Lowpass is more fun'); done(); }); }); describe('hint', function() { it('works', function(done) { const query2 = new Query({}); query2.hint({ indexAttributeA: 1, indexAttributeB: -1 }); assert.deepEqual(query2.options.hint, { indexAttributeA: 1, indexAttributeB: -1 }); const query3 = new Query({}); query3.hint('indexAttributeA_1'); assert.deepEqual(query3.options.hint, 'indexAttributeA_1'); done(); }); }); describe('snapshot', function() { it('works', function(done) { const query = new Query({}); query.snapshot(true); assert.equal(query.options.snapshot, true); done(); }); }); describe('batchSize', function() { it('works', function(done) { const query = new Query({}); query.batchSize(10); assert.equal(query.options.batchSize, 10); done(); }); }); describe('read', function() { const P = mongoose.mongo.ReadPreference; describe('without tags', function() { it('works', function(done) { const query = new Query({}); query.read('primary'); assert.ok(query.options.readPreference instanceof P); assert.ok(query.options.readPreference.isValid()); assert.equal(query.options.readPreference.mode, 'primary'); query.read('p'); assert.ok(query.options.readPreference instanceof P); assert.ok(query.options.readPreference.isValid()); assert.equal(query.options.readPreference.mode, 'primary'); query.read('primaryPreferred'); assert.ok(query.options.readPreference instanceof P); assert.ok(query.options.readPreference.isValid()); assert.equal(query.options.readPreference.mode, 'primaryPreferred'); query.read('pp'); assert.ok(query.options.readPreference instanceof P); assert.ok(query.options.readPreference.isValid()); assert.equal(query.options.readPreference.mode, 'primaryPreferred'); query.read('secondary'); assert.ok(query.options.readPreference instanceof P); assert.ok(query.options.readPreference.isValid()); assert.equal(query.options.readPreference.mode, 'secondary'); query.read('s'); assert.ok(query.options.readPreference instanceof P); assert.ok(query.options.readPreference.isValid()); assert.equal(query.options.readPreference.mode, 'secondary'); query.read('secondaryPreferred'); assert.ok(query.options.readPreference instanceof P); assert.ok(query.options.readPreference.isValid()); assert.equal(query.options.readPreference.mode, 'secondaryPreferred'); query.read('sp'); assert.ok(query.options.readPreference instanceof P); assert.ok(query.options.readPreference.isValid()); assert.equal(query.options.readPreference.mode, 'secondaryPreferred'); query.read('nearest'); assert.ok(query.options.readPreference instanceof P); assert.ok(query.options.readPreference.isValid()); assert.equal(query.options.readPreference.mode, 'nearest'); query.read('n'); assert.ok(query.options.readPreference instanceof P); assert.ok(query.options.readPreference.isValid()); assert.equal(query.options.readPreference.mode, 'nearest'); done(); }); }); describe('with tags', function() { it('works', function(done) { const query = new Query({}); const tags = [{ dc: 'sf', s: 1 }, { dc: 'jp', s: 2 }]; query.read('pp', tags); assert.ok(query.options.readPreference instanceof P); assert.ok(query.options.readPreference.isValid()); assert.equal(query.options.readPreference.mode, 'primaryPreferred'); assert.ok(Array.isArray(query.options.readPreference.tags)); assert.equal(query.options.readPreference.tags[0].dc, 'sf'); assert.equal(query.options.readPreference.tags[0].s, 1); assert.equal(query.options.readPreference.tags[1].dc, 'jp'); assert.equal(query.options.readPreference.tags[1].s, 2); done(); }); }); describe('inherits its models schema read option', function() { let schema, M, called; before(function() { schema = new Schema({}, { read: 'p' }); M = mongoose.model('schemaOptionReadPrefWithQuery', schema); }); it('if not set in query', function(done) { const options = M.where()._optionsForExec(M); assert.ok(options.readPreference instanceof P); assert.equal(options.readPreference.mode, 'primary'); done(); }); it('if set in query', function(done) { const options = M.where().read('s')._optionsForExec(M); assert.ok(options.readPreference instanceof P); assert.equal(options.readPreference.mode, 'secondary'); done(); }); it('and sends it though the driver', function(done) { const options = { read: 'secondary', writeConcern: { w: 'majority' } }; const schema = new Schema({ name: String }, options); const M = db.model('Test', schema); const q = M.find(); // stub the internal query options call const getopts = q._optionsForExec; q._optionsForExec = function(model) { q._optionsForExec = getopts; const ret = getopts.call(this, model); assert.ok(ret.readPreference); assert.equal(ret.readPreference.mode, 'secondary'); assert.deepEqual({ w: 'majority' }, ret.writeConcern); called = true; return ret; }; q.exec(function(err) { if (err) { return done(err); } assert.ok(called); done(); }); }); }); }); }); describe('setOptions', function() { it('works', function(done) { const q = new Query(); q.setOptions({ thing: 'cat' }); q.setOptions({ populate: ['fans'] }); q.setOptions({ batchSize: 10 }); q.setOptions({ limit: 4 }); q.setOptions({ skip: 3 }); q.setOptions({ sort: '-blah' }); q.setOptions({ sort: { woot: -1 } }); q.setOptions({ hint: { index1: 1, index2: -1 } }); q.setOptions({ read: ['s', [{ dc: 'eu' }]] }); assert.equal(q.options.thing, 'cat'); assert.deepEqual(q._mongooseOptions.populate.fans, { path: 'fans', _docs: {}, _childDocs: [] }); assert.equal(q.options.batchSize, 10); assert.equal(q.options.limit, 4); assert.equal(q.options.skip, 3); assert.equal(Object.keys(q.options.sort).length, 2); assert.equal(q.options.sort.blah, -1); assert.equal(q.options.sort.woot, -1); assert.equal(q.options.hint.index1, 1); assert.equal(q.options.hint.index2, -1); assert.equal(q.options.readPreference.mode, 'secondary'); assert.equal(q.options.readPreference.tags[0].dc, 'eu'); const Product = db.model('Product', productSchema); Product.create( { numbers: [3, 4, 5] }, { strings: 'hi there'.split(' ') }, function(err, doc1, doc2) { assert.ifError(err); Product.find().setOptions({ limit: 1, sort: { _id: -1 }, read: 'n' }).exec(function(err, docs) { assert.ifError(err); assert.equal(docs.length, 1); assert.equal(docs[0].id, doc2.id); done(); }); }); }); it('populate as array in options (gh-4446)', function(done) { const q = new Query(); q.setOptions({ populate: [{ path: 'path1' }, { path: 'path2' }] }); assert.deepEqual(Object.keys(q._mongooseOptions.populate), ['path1', 'path2']); done(); }); }); describe('getOptions', function() { const q = new Query(); q.limit(10); q.setOptions({ maxTimeMS: 1000 }); const opts = q.getOptions(); // does not use assert.deepEqual() because setOptions may alter the options internally assert.strictEqual(opts.limit, 10); assert.strictEqual(opts.maxTimeMS, 1000); }); describe('bug fixes', function() { describe('collations', function() { before(async function() { const _this = this; const version = await start.mongodVersion(); const mongo34 = version[0] > 3 || (version[0] === 3 && version[1] >= 4); if (!mongo34) { return _this.skip(); } }); it('collation support (gh-4839)', function(done) { const schema = new Schema({ name: String }); const MyModel = db.model('Test', schema); const collation = { locale: 'en_US', strength: 1 }; MyModel.create([{ name: 'a' }, { name: 'A' }]). then(function() { return MyModel.find({ name: 'a' }).collation(collation); }). then(function(docs) { assert.equal(docs.length, 2); return MyModel.find({ name: 'a' }, null, { collation: collation }); }). then(function(docs) { assert.equal(docs.length, 2); return MyModel.find({ name: 'a' }, null, { collation: collation }). sort({ _id: -1 }). cursor(). next(); }). then(function(doc) { assert.equal(doc.name, 'A'); return MyModel.find({ name: 'a' }); }). then(function(docs) { assert.equal(docs.length, 1); done(); }). catch(done); }); it('set on schema (gh-5295)', async function() { await db.db.collection('tests').drop().catch(err => { if (err.message === 'ns not found') { return; } throw err; }); const schema = new Schema({ name: String }, { collation: { locale: 'en_US', strength: 1 } }); const MyModel = db.model('Test', schema, 'tests'); await MyModel.create([{ name: 'a' }, { name: 'A' }]); const docs = await MyModel.find({ name: 'a' }); assert.equal(docs.length, 2); }); }); describe('gh-1950', function() { it.skip('ignores sort when passed to count', function(done) { const Product = db.model('Product', productSchema); Product.find().sort({ _id: 1 }).count({}).exec(function(error) { assert.ifError(error); done(); }); }); it('ignores sort when passed to countDocuments', function() { const Product = db.model('Product', productSchema); return Product.create({}). then(() => Product.find().sort({ _id: 1 }).countDocuments({}).exec()); }); it.skip('ignores count when passed to sort', function(done) { const Product = db.model('Product', productSchema); Product.find().count({}).sort({ _id: 1 }).exec(function(error) { assert.ifError(error); done(); }); }); }); it('excludes _id when select false and inclusive mode (gh-3010)', function(done) { const User = db.model('User', { _id: { select: false, type: Schema.Types.ObjectId, default: () => new mongoose.Types.ObjectId() }, username: String }); User.create({ username: 'Val' }, function(error, user) { assert.ifError(error); User.find({ _id: user._id }).select('username').exec(function(error, users) { assert.ifError(error); assert.equal(users.length, 1); assert.ok(!users[0]._id); assert.equal(users[0].username, 'Val'); done(); }); }); }); it('doesnt reverse key order for update docs (gh-3215)', function(done) { const Test = db.model('Test', { arr: [{ date: Date, value: Number }] }); const q = Test.updateOne({}, { $push: { arr: { $each: [{ date: new Date(), value: 1 }], $sort: { value: -1, date: -1 } } } }); assert.deepEqual(Object.keys(q.getUpdate().$push.arr.$sort), ['value', 'date']); done(); }); it('timestamps with $each (gh-4805)', function(done) { const nestedSchema = new Schema({ value: Number }, { timestamps: true }); const Test = db.model('Test', new Schema({ arr: [nestedSchema] }, { timestamps: true })); Test.updateOne({}, { $push: { arr: { $each: [{ value: 1 }] } } }).exec(function(error) { assert.ifError(error); done(); }); }); it.skip('allows sort with count (gh-3914)', function(done) { const Post = db.model('BlogPost', { title: String }); Post.count({}).sort({ title: 1 }).exec(function(error, count) { assert.ifError(error); assert.strictEqual(count, 0); done(); }); }); it.skip('allows sort with select (gh-3914)', function(done) { const Post = db.model('BlogPost', { title: String }); Post.count({}).select({ _id: 0 }).exec(function(error, count) { assert.ifError(error); assert.strictEqual(count, 0); done(); }); }); it('handles nested $ (gh-3265)', function(done) { const Post = db.model('BlogPost', { title: String, answers: [{ details: String, stats: { votes: Number, count: Number } }] }); const answersUpdate = { details: 'blah', stats: { votes: 1, count: '3' } }; const q = Post.updateOne( { 'answers._id': '507f1f77bcf86cd799439011' }, { $set: { 'answers.$': answersUpdate } }); assert.deepEqual(q.getUpdate().$set['answers.$'].stats, { votes: 1, count: 3 }); done(); }); it('$geoWithin with single nested schemas (gh-4044)', function(done) { const locationSchema = new Schema({ type: { type: String }, coordinates: [] }, { _id: false }); const schema = new Schema({ title: String, location: { type: locationSchema, required: true } }); schema.index({ location: '2dsphere' }); const Model = db.model('Test', schema); const query = { location: { $geoWithin: { $geometry: { type: 'Polygon', coordinates: [[[-1, 0], [-1, 3], [4, 3], [4, 0], [-1, 0]]] } } } }; Model.find(query, function(error) { assert.ifError(error); done(); }); }); it('setDefaultsOnInsert with empty update (gh-3825)', function(done) { const schema = new mongoose.Schema({ test: { type: Number, default: 8472 }, name: String }); const MyModel = db.model('Test', schema); const opts = { upsert: true }; MyModel.updateOne({}, {}, opts, function(error) { assert.ifError(error); MyModel.findOne({}, function(error, doc) { assert.ifError(error); assert.ok(doc); assert.strictEqual(doc.test, 8472); assert.ok(!doc.name); done(); }); }); }); it('custom query methods (gh-3714)', function(done) { const schema = new mongoose.Schema({ name: String }); schema.query.byName = function(name) { return this.find({ name: name }); }; const MyModel = db.model('Test', schema); MyModel.create({ name: 'Val' }, function(error) { assert.ifError(error); MyModel.find().byName('Val').exec(function(error, docs) { assert.ifError(error); assert.equal(docs.length, 1); assert.equal(docs[0].name, 'Val'); done(); }); }); }); it('string as input (gh-4378)', function(done) { const schema = new mongoose.Schema({ name: String }); const MyModel = db.model('Test', schema); MyModel.findOne('', function(error) { assert.ok(error); assert.equal(error.name, 'ObjectParameterError'); done(); }); }); it('handles geoWithin with $center and mongoose object (gh-4419)', function(done) { const areaSchema = new Schema({ name: String, circle: Array }); const Area = db.model('Test', areaSchema); const placeSchema = new Schema({ name: String, geometry: { type: { type: String, enum: ['Point'], default: 'Point' }, coordinates: { type: [Number] } } }); placeSchema.index({ geometry: '2dsphere' }); const Place = db.model('Place', placeSchema); const tromso = new Area({ name: 'Tromso, Norway', circle: [[18.89, 69.62], 10 / 3963.2] }); tromso.save(function(error) { assert.ifError(error); const airport = { name: 'Center', geometry: { type: 'Point', coordinates: [18.895, 69.67] } }; Place.create(airport, function(error) { assert.ifError(error); const q = { geometry: { $geoWithin: { $centerSphere: tromso.circle } } }; Place.find(q).exec(function(error, docs) { assert.ifError(error); assert.equal(docs.length, 1); assert.equal(docs[0].name, 'Center'); done(); }); }); }); }); it('$not with objects (gh-4495)', function(done) { const schema = new Schema({ createdAt: Date }); const M = db.model('Test', schema); const q = M.find({ createdAt: { $not: { $gte: '2016/09/02 00:00:00', $lte: '2016/09/02 23:59:59' } } }); q._castConditions(); assert.ok(q._conditions.createdAt.$not.$gte instanceof Date); assert.ok(q._conditions.createdAt.$not.$lte instanceof Date); done(); }); it('geoIntersects with mongoose doc as coords (gh-4408)', function(done) { const lineStringSchema = new Schema({ name: String, geo: { type: { type: String, default: 'LineString' }, coordinates: [[Number]] } }); const LineString = db.model('Test', lineStringSchema); const ls = { name: 'test', geo: { coordinates: [[14.59, 24.847], [28.477, 15.961]] } }; const ls2 = { name: 'test2', geo: { coordinates: [[27.528, 25.006], [14.063, 15.591]] } }; LineString.create(ls, ls2, function(error, ls1) { assert.ifError(error); const query = { geo: { $geoIntersects: { $geometry: { type: 'LineString', coordinates: ls1.geo.coordinates } } } }; LineString.find(query, function(error, results) { assert.ifError(error); assert.equal(results.length, 2); done(); }); }); }); it('string with $not (gh-4592)', function(done) { const TestSchema = new Schema({ test: String }); const Test = db.model('Test', TestSchema); Test.findOne({ test: { $not: /test/ } }, function(error) { assert.ifError(error); done(); }); }); it('does not cast undefined to null in mongoose (gh-6236)', async function() { const TestSchema = new Schema({ test: String }); const Test = db.model('Test', TestSchema); await Test.create({}); const q = Test.find({ test: void 0 }); const res = await q.exec(); assert.strictEqual(q.getFilter().test, void 0); assert.ok('test' in q.getFilter()); assert.equal(res.length, 1); }); it('runs query setters with _id field (gh-5351)', function(done) { const testSchema = new Schema({ val: { type: String } }); const Test = db.model('Test', testSchema); Test.create({ val: 'A string' }). then(function() { return Test.findOne({}); }). then(function(doc) { return Test.findOneAndUpdate({ _id: doc._id }, { $set: { val: 'another string' } }, { new: true }); }). then(function(doc) { assert.ok(doc); assert.equal(doc.val, 'another string'); }). then(done). catch(done); }); it('runs setters if query field is an array (gh-6277)', async function() { const setterCalled = []; const schema = new Schema({ strings: { type: [String], set: v => { setterCalled.push(v); return v; } } }); const Model = db.model('Test', schema);
await Model.find({ strings: 'test' }); assert.equal(setterCalled.length, 0); await Model.find({ strings: ['test'] }); assert.equal(setterCalled.length, 1); assert.deepEqual(setterCalled, [['test']]); }); it('$exists under $not (gh-4933)', function(done) { const TestSchema = new Schema({ test: String }); const Test = db.model('Test', TestSchema); Test.findOne({ test: { $not: { $exists: true } } }, function(error) { assert.ifError(error); done(); }); }); it('geojson underneath array (gh-5467)', function(done) { const storySchema = new Schema({ name: String, gallery: [{ src: String, location: { type: { type: String, enum: ['Point'] }, coordinates: { type: [Number], default: void 0 } }, timestamp: Date }] }); storySchema.index({ 'gallery.location': '2dsphere' }); const Story = db.model('Story', storySchema); const q = { 'gallery.location': { $near: { $geometry: { type: 'Point', coordinates: [51.53377166666667, -0.1197471666666667] }, $maxDistance: 500 } } }; Story.once('index', function(error) { assert.ifError(error); Story.updateOne(q, { name: 'test' }, { upsert: true }, function(error) { assert.ifError(error); done(); }); }); }); it('slice respects schema projections (gh-5450)', function(done) { const gameSchema = Schema({ name: String, developer: { type: String, select: false }, arr: [Number] }); const Game = db.model('Test', gameSchema); Game.create({ name: 'Mass Effect', developer: 'BioWare', arr: [1, 2, 3] }, function(error) { assert.ifError(error); Game.findOne({ name: 'Mass Effect' }).slice({ arr: 1 }).exec(function(error, doc) { assert.ifError(error); assert.equal(doc.name, 'Mass Effect'); assert.deepEqual(doc.toObject().arr, [1]); assert.ok(!doc.developer); done(); }); }); }); it('overwrites when passing an object when path already set to primitive (gh-6097)', function() { const schema = new mongoose.Schema({ status: String }); const Model = db.model('Test', schema); return Model. where({ status: 'approved' }). where({ status: { $ne: 'delayed' } }); }); it('$exists for arrays and embedded docs (gh-4937)', function(done) { const subSchema = new Schema({ name: String }); const TestSchema = new Schema({ test: [String], sub: subSchema }); const Test = db.model('Test', TestSchema); const q = { test: { $exists: true }, sub: { $exists: false } }; Test.findOne(q, function(error) { assert.ifError(error); done(); }); }); it('report error in pre hook (gh-5520)', function(done) { const TestSchema = new Schema({ name: String }); const ops = [ 'count', 'find', 'findOne', 'findOneAndRemove', 'findOneAndUpdate', 'replaceOne', 'update', 'updateOne', 'updateMany' ]; ops.forEach(function(op) { TestSchema.pre(op, function(next) { this.error(new Error(op + ' error')); next(); }); }); const TestModel = db.model('Test', TestSchema); let numOps = ops.length; ops.forEach(function(op) { TestModel.find({}).updateOne({ name: 'test' })[op](function(error) { assert.ok(error); assert.equal(error.message, op + ' error'); --numOps || done(); }); }); }); it('cast error with custom error (gh-5520)', function(done) { const TestSchema = new Schema({ name: Number }); const TestModel = db.model('Test', TestSchema); TestModel. find({ name: 'not a number' }). error(new Error('woops')). exec(function(error) { assert.ok(error); // CastError check happens **after** `.error()` assert.equal(error.name, 'CastError'); done(); }); }); it('change deleteOne to updateOne for soft deletes using $isDeleted (gh-4428)', function(done) { const schema = new mongoose.Schema({ name: String, isDeleted: Boolean }); schema.pre('remove', function(next) { const _this = this; this.constructor.updateOne({ isDeleted: true }, function(error) { // Force mongoose to consider this doc as deleted. _this.$isDeleted(true); next(error); }); }); const M = db.model('Test', schema); M.create({ name: 'test' }, function(error, doc) { assert.ifError(error); doc.remove(function(error) { assert.ifError(error); M.findById(doc._id, function(error, doc) { assert.ifError(error); assert.ok(doc); assert.equal(doc.isDeleted, true); done(); }); }); }); }); it('child schema with select: false in multiple paths (gh-5603)', function(done) { const ChildSchema = new mongoose.Schema({ field: { type: String, select: false }, _id: false }, { id: false }); const ParentSchema = new mongoose.Schema({ child: ChildSchema, child2: ChildSchema }); const Parent = db.model('Parent', ParentSchema); const ogParent = new Parent(); ogParent.child = { field: 'test' }; ogParent.child2 = { field: 'test' }; ogParent.save(function(error) { assert.ifError(error); Parent.findById(ogParent._id).exec(function(error, doc) { assert.ifError(error); assert.ok(!doc.child.field); assert.ok(!doc.child2.field); done(); }); }); }); it('errors in post init (gh-5592)', function(done) { const TestSchema = new Schema(); let count = 0; TestSchema.post('init', function() { throw new Error('Failed! ' + (count++)); }); const TestModel = db.model('Test', TestSchema); const docs = []; for (let i = 0; i < 10; ++i) { docs.push({}); } TestModel.create(docs, function(error) { assert.ifError(error); TestModel.find({}, function(error) { assert.ok(error); assert.equal(error.message, 'Failed! 0'); assert.equal(count, 10); done(); }); }); }); it('with non-object args (gh-1698)', function(done) { const schema = new mongoose.Schema({ email: String }); const M = db.model('Test', schema); M.find(42, function(error) { assert.ok(error); assert.equal(error.name, 'ObjectParameterError'); done(); }); }); describe('throw', function() { let listeners; beforeEach(function() { listeners = process.listeners('uncaughtException'); process.removeAllListeners('uncaughtException'); }); afterEach(function() { process.on('uncaughtException', listeners[0]); }); it('throw on sync exceptions in callbacks (gh-6178)', function(done) { const schema = new Schema({}); const Test = db.model('Test', schema); process.once('uncaughtException', err => { assert.equal(err.message, 'Oops!'); done(); }); Test.find({}, function() { throw new Error('Oops!'); }); }); }); it.skip('set overwrite after update() (gh-4740)', async function() { const schema = new Schema({ name: String, age: Number }); const User = db.model('User', schema);
await User.create({ name: 'Bar', age: 29 }); await User.where({ name: 'Bar' }). update({ name: 'Baz' }). setOptions({ overwrite: true }); const doc = await User.findOne(); assert.equal(doc.name, 'Baz'); assert.ok(!doc.age); }); it('queries with BSON overflow (gh-5812)', function(done) { this.timeout(10000); const schema = new mongoose.Schema({ email: String }); const model = db.model('Test', schema); const bigData = new Array(800000); for (let i = 0; i < bigData.length; ++i) { bigData[i] = 'test1234567890'; } model.find({ email: { $in: bigData } }).lean(). then(function() { done(new Error('Expected an error')); }). catch(function(error) { assert.ok(error); assert.ok(error.message !== 'Expected error'); done(); }); }); it('consistently return query when callback specified (gh-6271)', function(done) { const schema = new mongoose.Schema({ n: Number }); const Model = db.model('Test', schema); Model.create({ n: 0 }, (err, doc) => { assert.ifError(err); const updateQuery = Model.findOneAndUpdate({ _id: doc._id }, { $inc: { n: 1 } }, { new: true }, (err, doc) => { assert.ifError(err); assert.equal(doc.n, 1); done(); }); assert.ok(updateQuery instanceof Query); }); }); it('explain() (gh-6625)', async function() { const schema = new mongoose.Schema({ n: Number }); const Model = db.model('Test', schema); await Model.create({ n: 42 }); let res = await Model.find().explain('queryPlanner'); assert.ok(res.queryPlanner); res = await Model.find().explain(); assert.ok(res.queryPlanner); res = await Model.find().explain().explain(false); assert.equal(res[0].n, 42); }); it('cast embedded discriminators with dot notation (gh-6027)', async function() { const ownerSchema = new Schema({ _id: false }, { discriminatorKey: 'type' }); const userOwnerSchema = new Schema({ id: { type: Schema.Types.ObjectId, required: true } }, { _id: false }); const tagOwnerSchema = new Schema({ id: { type: String, required: true } }, { _id: false }); const activitySchema = new Schema({ owner: { type: ownerSchema, required: true } }, { _id: false }); activitySchema.path('owner').discriminator('user', userOwnerSchema); activitySchema.path('owner').discriminator('tag', tagOwnerSchema); const Activity = db.model('Test', activitySchema); await Activity.insertMany([ { owner: { id: '5a042f742a91c1db447534d5', type: 'user' } }, { owner: { id: 'asdf', type: 'tag' } } ]); const activity = await Activity.findOne({ 'owner.type': 'user', 'owner.id': '5a042f742a91c1db447534d5' }); assert.ok(activity); assert.equal(activity.owner.type, 'user'); }); it('cast embedded discriminators with embedded obj (gh-6027)', async function() { const ownerSchema = new Schema({ _id: false }, { discriminatorKey: 'type' }); const userOwnerSchema = new Schema({ id: { type: Schema.Types.ObjectId, required: true } }, { _id: false }); const tagOwnerSchema = new Schema({ id: { type: String, required: true } }, { _id: false }); const activitySchema = new Schema({ owner: { type: ownerSchema, required: true } }, { _id: false }); activitySchema.path('owner').discriminator('user', userOwnerSchema); activitySchema.path('owner').discriminator('tag', tagOwnerSchema); const Activity = db.model('Test', activitySchema); await Activity.insertMany([ { owner: { id: '5a042f742a91c1db447534d5', type: 'user' } }, { owner: { id: 'asdf', type: 'tag' } } ]); const activity = await Activity.findOne({ owner: { type: 'user', id: '5a042f742a91c1db447534d5' } }); assert.ok(activity); assert.equal(activity.owner.type, 'user'); }); it('cast embedded discriminators with $elemMatch discriminator key (gh-7449)', async function() { const ListingLineSchema = new Schema({ sellerId: Number }, { strictQuery: false }); const OrderSchema = new Schema({ lines: [new Schema({ amount: Number }, { discriminatorKey: 'kind', strictQuery: false })] }); OrderSchema.path('lines').discriminator('listing', ListingLineSchema); const Order = db.model('Order', OrderSchema); await Order.create({ lines: { kind: 'listing', sellerId: 42 } }); let count = await Order.countDocuments({ lines: { $elemMatch: { kind: 'listing', sellerId: '42' } } }); assert.strictEqual(count, 1); count = await Order.countDocuments({ lines: { $elemMatch: { sellerId: '42' } } }); assert.strictEqual(count, 0); }); it('handles geoWithin with mongoose docs (gh-4392)', async function() { const areaSchema = new Schema({ name: { type: String }, loc: { type: { type: String, enum: ['Polygon'], default: 'Polygon' }, coordinates: [[[Number]]] } }); const Area = db.model('Test', areaSchema); const observationSchema = new Schema({ geometry: { type: { type: String, enum: ['Point'], default: 'Point' }, coordinates: { type: [Number] } }, properties: { temperature: { type: Number } } }); observationSchema.index({ geometry: '2dsphere' }); const Observation = db.model('Test1', observationSchema); await Observation.init();
const tromso = new Area({ name: 'Tromso, Norway', loc: { type: 'Polygon', coordinates: [[ [18.89, 69.62], [18.89, 69.72], [19.03, 69.72], [19.03, 69.62], [18.89, 69.62] ]] } }); await tromso.save(); const observation = { geometry: { type: 'Point', coordinates: [18.895, 69.67] } }; await Observation.create(observation);
const docs = await Observation. find(). where('geometry').within().geometry(tromso.loc). exec(); assert.equal(docs.length, 1); }); }); describe('handles falsy and object projections with defaults (gh-3256)', function() { let MyModel; before(function() { const PersonSchema = new Schema({ name: String, lastName: String, dependents: [String], salary: { type: Number, default: 25000 } }); db.deleteModel(/Person/); MyModel = db.model('Person', PersonSchema); }); beforeEach(async function() { await MyModel.collection.insertOne({ name: 'John', lastName: 'Doe', dependents: ['Jake', 'Jill', 'Jane'] }); }); it('falsy projection', function(done) { MyModel.findOne({ name: 'John' }, { lastName: false }). exec(function(error, person) { assert.ifError(error); assert.equal(person.salary, 25000); done(); }); }); it('slice projection', function(done) { MyModel.findOne({ name: 'John' }, { dependents: { $slice: 1 } }).exec(function(error, person) { assert.ifError(error); assert.equal(person.salary, 25000); done(); }); }); it('empty projection', function(done) { MyModel.findOne({ name: 'John' }, {}). exec(function(error, person) { assert.ifError(error); assert.equal(person.salary, 25000); done(); }); }); }); describe('count', function() { it('calls utils.toObject on conditions (gh-6323)', async function() { const priceSchema = new Schema({ key: String, price: Number }); const Model = db.model('Test', priceSchema); const tests = []; for (let i = 0; i < 10; i++) { const p = i * 25; tests.push(new Model({ key: 'value', price: p })); } const query = { key: 'value' }; const priceQuery = Object.create(null); priceQuery.$gte = 0; priceQuery.$lte = 200; Object.assign(query, { price: priceQuery }); await Model.create(tests); const count = await Model.countDocuments(query); assert.strictEqual(count, 9); }); }); describe('setQuery', function() { it('replaces existing query with new value (gh-6854)', function() { const q = new Query({}); q.where('userName').exists(); q.setQuery({ a: 1 }); assert.deepStrictEqual(q._conditions, { a: 1 }); }); }); it('map (gh-7142)', async function() { const Model = db.model('Test', new Schema({ name: String }));
await Model.create({ name: 'test' }); const now = new Date(); const res = await Model.findOne().transform(res => { res.loadedAt = now; return res; }); assert.equal(res.loadedAt, now); }); describe('orFail (gh-6841)', function() { let Model; before(function() { db.deleteModel(/Test/); Model = db.model('Test', new Schema({ name: String })); }); beforeEach(function() { return Model.deleteMany({}).then(() => Model.create({ name: 'Test' })); }); it('find()', async function() { let threw = false; try { await Model.find({ name: 'na' }).orFail(() => new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); // Shouldn't throw const res = await Model.find({ name: 'Test' }).orFail(new Error('Oops')); assert.equal(res[0].name, 'Test'); }); it('findOne()', async function() { let threw = false; try { await Model.findOne({ name: 'na' }).orFail(() => new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); // Shouldn't throw const res = await Model.findOne({ name: 'Test' }).orFail(new Error('Oops')); assert.equal(res.name, 'Test'); }); it('deleteMany()', async function() { let threw = false; try { await Model.deleteMany({ name: 'na' }).orFail(new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); // Shouldn't throw const res = await Model.deleteMany({ name: 'Test' }).orFail(new Error('Oops')); assert.equal(res.deletedCount, 1); }); it('deleteOne()', async function() { let threw = false; try { await Model.deleteOne({ name: 'na' }).orFail(new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); // Shouldn't throw const res = await Model.deleteOne({ name: 'Test' }).orFail(new Error('Oops')); assert.equal(res.deletedCount, 1); }); it('remove()', async function() { let threw = false; try { await Model.remove({ name: 'na' }).orFail(new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); // Shouldn't throw const res = await Model.remove({ name: 'Test' }).orFail(new Error('Oops')); assert.equal(res.deletedCount, 1); }); it('replaceOne()', async function() { let threw = false; try { await Model.replaceOne({ name: 'na' }, { name: 'bar' }).orFail(new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); // Shouldn't throw const res = await Model.replaceOne({ name: 'Test' }, { name: 'bar' }).orFail(new Error('Oops')); assert.equal(res.modifiedCount, 1); }); it('update()', async function() { let threw = false; try { await Model.update({ name: 'na' }, { name: 'foo' }). orFail(new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); // Shouldn't throw const res = await Model.update({}, { name: 'Test2' }).orFail(new Error('Oops')); assert.equal(res.modifiedCount, 1); }); it('updateMany()', async function() { let threw = false; try { await Model.updateMany({ name: 'na' }, { name: 'foo' }). orFail(new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); // Shouldn't throw const res = await Model.updateMany({}, { name: 'Test2' }).orFail(new Error('Oops')); assert.equal(res.modifiedCount, 1); }); it('updateOne()', async function() { let threw = false; try { await Model.updateOne({ name: 'na' }, { name: 'foo' }). orFail(new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); // Shouldn't throw const res = await Model.updateOne({}, { name: 'Test2' }).orFail(new Error('Oops')); assert.equal(res.modifiedCount, 1); }); it('findOneAndUpdate()', async function() { let threw = false; try { await Model.findOneAndUpdate({ name: 'na' }, { name: 'foo' }). orFail(new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); // Shouldn't throw const res = await Model.findOneAndUpdate({}, { name: 'Test2' }).orFail(new Error('Oops')); assert.equal(res.name, 'Test'); }); it('findOneAndDelete()', async function() { let threw = false; try { await Model.findOneAndDelete({ name: 'na' }). orFail(new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); // Shouldn't throw const res = await Model.findOneAndDelete({ name: 'Test' }).orFail(new Error('Oops')); assert.equal(res.name, 'Test'); }); it('executes before post hooks (gh-7280)', async function() { const schema = new Schema({ name: String }); const docs = []; schema.post('findOne', function(doc, next) { docs.push(doc); next(); }); const Model = db.model('Test2', schema); await Model.create({ name: 'Test' }); let threw = false; try { await Model.findOne({ name: 'na' }).orFail(new Error('Oops!')); } catch (error) { assert.ok(error); assert.equal(error.message, 'Oops!'); threw = true; } assert.ok(threw); assert.equal(docs.length, 0); // Shouldn't throw const res = await Model.findOne({ name: 'Test' }).orFail(new Error('Oops')); assert.equal(res.name, 'Test'); assert.equal(docs.length, 1); }); it('throws DocumentNotFoundError by default execute (gh-7409)', async function() { const err = await Model.findOne({ name: 'na' }). orFail(). then(() => null, err => err); assert.equal(err.name, 'DocumentNotFoundError', err.stack); assert.ok(err.message.indexOf('na') !== -1, err.message); assert.ok(err.message.indexOf('"Test"') !== -1, err.message); assert.deepEqual(err.filter, { name: 'na' }); }); }); describe('getPopulatedPaths', function() { it('doesn\'t break on a query without population (gh-6677)', async function() { const schema = new Schema({ name: String }); schema.pre('findOne', function() { assert.deepStrictEqual(this.getPopulatedPaths(), []); }); const Model = db.model('Test', schema);
await Model.findOne({}); }); it('returns an array of populated paths as strings (gh-6677)', async function() { const otherSchema = new Schema({ name: String }); const schema = new Schema({ other: { type: Schema.Types.ObjectId, ref: 'Test1' } }); schema.pre('findOne', function() { assert.deepStrictEqual(this.getPopulatedPaths(), ['other']); }); const Other = db.model('Test1', otherSchema); const Test = db.model('Test', schema); const other = new Other({ name: 'one' }); const test = new Test({ other: other._id });
await other.save(); await test.save(); await Test.findOne({}).populate('other'); }); it('returns deep populated paths (gh-7757)', function() { db.model('Test3', new Schema({ name: String })); db.model('Test2', new Schema({ level3: { type: String, ref: 'Test3' } })); const L1 = db.model('Test', new Schema({ level1: { type: String, ref: 'Test2' } })); const query = L1.find().populate({ path: 'level1', populate: { path: 'level2', populate: { path: 'level3' } } }); assert.deepEqual(query.getPopulatedPaths(), ['level1', 'level1.level2', 'level1.level2.level3']); }); }); describe('setUpdate', function() { it('replaces existing update doc with new value', function() { const q = new Query({}); q.set('testing', '123'); q.setUpdate({ $set: { newPath: 'newValue' } }); assert.strictEqual(q._update.$set.testing, undefined); assert.strictEqual(q._update.$set.newPath, 'newValue'); }); }); describe('get() (gh-7312)', function() { it('works with using $set', function() { const q = new Query({}); q.updateOne({}, { $set: { name: 'Jean-Luc Picard' } }); assert.equal(q.get('name'), 'Jean-Luc Picard'); }); it('works with $set syntactic sugar', function() { const q = new Query({}); q.updateOne({}, { name: 'Jean-Luc Picard' }); assert.equal(q.get('name'), 'Jean-Luc Picard'); }); it('works with mixed', function() { const q = new Query({}); q.updateOne({}, { name: 'Jean-Luc Picard', $set: { age: 59 } }); assert.equal(q.get('name'), 'Jean-Luc Picard'); }); it('$set overwrites existing', function() { const M = db.model('Test', new Schema({ name: String })); const q = M.updateOne({}, { name: 'Jean-Luc Picard', $set: { name: 'William Riker' } }, { upsert: true }); assert.equal(q.get('name'), 'Jean-Luc Picard'); return q.exec(). then(() => M.findOne()). then(doc => assert.equal(doc.name, 'Jean-Luc Picard')); }); }); it('allows skipping timestamps in updateOne() (gh-6980)', async function() { const schema = new Schema({ name: String }, { timestamps: true }); const M = db.model('Test', schema);
const doc = await M.create({ name: 'foo' }); assert.ok(doc.createdAt); assert.ok(doc.updatedAt); const start = Date.now(); await new Promise((resolve) => setTimeout(resolve, 10)); const opts = { timestamps: false, new: true }; const res = await M.findOneAndUpdate({}, { name: 'bar' }, opts); assert.equal(res.name, 'bar'); assert.ok(res.updatedAt.valueOf() <= start, `Expected ${res.updatedAt.valueOf()} <= ${start}`); }); it('increments timestamps for nested subdocs (gh-4412)', async function() { const childSchema = new Schema({ name: String }, { timestamps: true, versionKey: false }); const parentSchema = new Schema({ child: childSchema }, { // timestamps: true, versionKey: false }); const Parent = db.model('Parent', parentSchema);
let doc = await Parent.create({ child: { name: 'foo' } }); assert.ok(doc.child.updatedAt); assert.ok(doc.child.createdAt); let start = Date.now(); await new Promise((resolve) => setTimeout(resolve, 10)); await Parent.updateOne({}, { $set: { 'child.name': 'Luke' } }); doc = await Parent.findOne(); let updatedAt = doc.child.updatedAt.valueOf(); assert.ok(updatedAt > start, `Expected ${updatedAt} > ${start}`); // Overwrite whole doc start = Date.now(); await new Promise((resolve) => setTimeout(resolve, 10)); await Parent.updateOne({}, { $set: { child: { name: 'Luke' } } }); doc = await Parent.findOne(); const createdAt = doc.child.createdAt.valueOf(); updatedAt = doc.child.updatedAt.valueOf(); assert.ok(createdAt > start, `Expected ${createdAt} > ${start}`); assert.ok(updatedAt > start, `Expected ${updatedAt} > ${start}`); }); describe('increments timestamps for arrays of nested subdocs (gh-4412)', function() { let Parent; before(function() { const childSchema = new Schema({ name: String }, { timestamps: true, versionKey: false }); const parentSchema = new Schema({ children: [childSchema] }, { versionKey: false }); Parent = db.model('Parent', parentSchema); }); it('$set nested property with numeric position', async function() { const kids = 'foo bar baz'.split(' ').map(n => { return { name: `${n}` };}); const doc = await Parent.create({ children: kids }); assert.ok(doc.children[0].updatedAt && doc.children[0].createdAt); assert.ok(doc.children[1].updatedAt && doc.children[1].createdAt); assert.ok(doc.children[2].updatedAt && doc.children[2].createdAt); const start = new Date(); await new Promise((resolve) => setTimeout(resolve, 10)); const cond = {}; const update = { $set: { 'children.0.name': 'Luke' } }; await Parent.updateOne(cond, update); const found = await Parent.findOne({}); const updatedAt = found.children[0].updatedAt; const name = found.children[0].name; assert.ok(name, 'Luke'); assert.ok(updatedAt.valueOf() > start.valueOf(), `Expected ${updatedAt} > ${start}`); }); it('$set numeric element', async function() { const kids = 'foo bar baz'.split(' ').map(n => { return { name: `${n}` };}); const doc = await Parent.create({ children: kids }); assert.ok(doc.children[0].updatedAt && doc.children[0].createdAt); assert.ok(doc.children[1].updatedAt && doc.children[1].createdAt); assert.ok(doc.children[2].updatedAt && doc.children[2].createdAt); const start = Date.now(); await new Promise((resolve) => setTimeout(resolve, 10)); const cond = {}; const update = { $set: { 'children.0': { name: 'Luke' } } }; await Parent.updateOne(cond, update); const found = await Parent.findOne({}); const updatedAt = found.children[0].updatedAt.valueOf(); const name = found.children[0].name; assert.ok(name, 'Luke'); assert.ok(updatedAt > start, `Expected ${updatedAt} > ${start}`); }); it('$set with positional operator', async function() { const kids = 'foo bar baz'.split(' ').map(n => { return { name: `${n}` };}); const doc = await Parent.create({ children: kids }); assert.ok(doc.children[0].updatedAt && doc.children[0].createdAt); assert.ok(doc.children[1].updatedAt && doc.children[1].createdAt); assert.ok(doc.children[2].updatedAt && doc.children[2].createdAt); const start = Date.now(); await new Promise((resolve) => setTimeout(resolve, 10)); const cond = { 'children.name': 'bar' }; const update = { $set: { 'children.$.name': 'Luke' } }; await Parent.updateOne(cond, update); const found = await Parent.findOne({}); const updatedAt = found.children[1].updatedAt.valueOf(); const name = found.children[1].name; assert.ok(name, 'Luke'); assert.ok(updatedAt > start, `Expected ${updatedAt} > ${start}`); }); it('$set with positional operator and array (gh-7106)', async function() { const subSub = new Schema({ x: String }); const sub = new Schema({ name: String, subArr: [subSub] }); const schema = new Schema({ arr: [sub] }, { timestamps: true }); const Test = db.model('Test', schema); const cond = { arr: { $elemMatch: { name: 'abc' } } }; const set = { $set: { 'arr.$.subArr': [{ x: 'b' }] } }; await Test.create({ arr: [{ name: 'abc', subArr: [{ x: 'a' }] }] }); await Test.updateOne(cond, set); const res = await Test.collection.findOne({}); assert.ok(Array.isArray(res.arr)); assert.strictEqual(res.arr[0].subArr[0].x, 'b'); }); }); it('strictQuery option (gh-4136) (gh-7178)', async function() { const modelSchema = new Schema({ field: Number, nested: { path: String } }, { strictQuery: 'throw' }); const Model = db.model('Test', modelSchema); // `find()` on a top-level path not in the schema let err = await Model.find({ notInschema: 1 }).then(() => null, e => e); assert.ok(err); assert.ok(err.message.indexOf('strictQuery') !== -1, err.message); // Shouldn't throw on nested path re: gh-7178 await Model.create({ nested: { path: 'a' } }); const doc = await Model.findOne({ nested: { path: 'a' } }); assert.ok(doc); // `find()` on a nested path not in the schema err = await Model.find({ 'nested.bad': 'foo' }).then(() => null, e => e); assert.ok(err); assert.ok(err.message.indexOf('strictQuery') !== -1, err.message); }); it('strictQuery inherits from strict (gh-10763) (gh-4136) (gh-7178)', async function() { const modelSchema = new Schema({ field: Number, nested: { path: String } }, { strict: 'throw' }); const Model = db.model('Test', modelSchema); // `find()` on a top-level path not in the schema const err = await Model.find({ notInschema: 1 }).then(() => null, e => e); assert.ok(err); assert.ok(err.message.indexOf('strictQuery') !== -1, err.message); }); it('strictQuery = true (gh-6032)', async function() { const modelSchema = new Schema({ field: Number }, { strictQuery: true }); const Model = db.model('Test', modelSchema); await Model.create({ field: 1 }); const docs = await Model.find({ nonexistingField: 1 }); assert.equal(docs.length, 1); }); it('function defaults run after query result is inited (gh-7182)', async function() { const schema = new Schema({ kind: String, hasDefault: String }); schema.path('hasDefault').default(function() { return this.kind === 'test' ? 'success' : 'fail'; });
const Model = db.model('Test', schema); await Model.create({ kind: 'test' }); await Model.updateOne({}, { $unset: { hasDefault: 1 } }); const doc = await Model.findOne(); assert.equal(doc.hasDefault, 'success'); }); it('merging objectids with where() (gh-7360)', function() { const Test = db.model('Test', new Schema({})); return Test.create({}). then(doc => Test.find({ _id: doc._id.toString() }).where({ _id: doc._id })). then(res => assert.equal(res.length, 1)); }); it('maxTimeMS() (gh-7254)', async function() { const Model = db.model('Test', new Schema({}));
await Model.create({}); const res = await Model.find({ $where: 'sleep(1000) || true' }). maxTimeMS(10). then(() => null, err => err); assert.ok(res); assert.ok(res.message.indexOf('time limit') !== -1, res.message); }); it('connection-level maxTimeMS() (gh-4066)', async function() { db.options = db.options || {}; db.options.maxTimeMS = 10; const Model = db.model('Test', new Schema({}));
await Model.create({}); const res = await Model.find({ $where: 'sleep(250) || true' }). then(() => null, err => err); assert.ok(res); assert.ok(res.message.indexOf('time limit') !== -1, res.message); delete db.options.maxTimeMS; }); it('mongoose-level maxTimeMS() (gh-4066)', async function() { db.base.options = db.base.options || {}; db.base.options.maxTimeMS = 10; const Model = db.model('Test', new Schema({}));
await Model.create({}); const res = await Model.find({ $where: 'sleep(250) || true' }). then(() => null, err => err); assert.ok(res); assert.ok(res.message.indexOf('time limit') !== -1, res.message); delete db.base.options.maxTimeMS; }); it('throws error with updateOne() and overwrite (gh-7475)', function() { const Model = db.model('Test', Schema({ name: String })); return Model.updateOne({}, { name: 'bar' }, { overwrite: true }).then( () => { throw new Error('Should have failed'); }, err => assert.ok(err.message.indexOf('updateOne') !== -1) ); }); it('sets deletedCount on result of remove() (gh-7629)', async function() { const schema = new Schema({ name: String }); const Model = db.model('Test', schema);
await Model.create({ name: 'foo' }); let res = await Model.remove({}); assert.equal(res.deletedCount, 1); res = await Model.remove({}); assert.strictEqual(res.deletedCount, 0); }); describe('merge()', function() { it('copies populate() (gh-1790)', async function() { const Car = db.model('Car', { color: String, model: String, owner: { type: Schema.Types.ObjectId, ref: 'Person' } }); const Person = db.model('Person', { name: String });
const val = await Person.create({ name: 'Val' }); await Car.create({ color: 'Brown', model: 'Subaru', owner: val._id }); const q = Car.findOne().populate('owner'); const res = await Car.findOne().merge(q); assert.equal(res.owner.name, 'Val'); }); }); describe('Query#validate() (gh-7984)', function() { it('middleware', function() { const schema = new Schema({ password: { type: String, validate: v => v.length >= 6, required: true } }); let docCalls = 0; schema.post('validate', function() { ++docCalls; }); let queryCalls = 0; schema.post('validate', { query: true }, function() { ++queryCalls; const pw = this.get('password'); assert.equal(pw, '6chars'); this.set('password', 'encryptedpassword'); }); const M = db.model('Test', schema); const opts = { runValidators: true, upsert: true, new: true }; return M.findOneAndUpdate({}, { password: '6chars' }, opts).then(doc => { assert.equal(docCalls, 0); assert.equal(queryCalls, 1); assert.equal(doc.password, 'encryptedpassword'); }); }); it('pre("validate") errors (gh-7187)', async function() { const addressSchema = Schema({ countryId: String }); addressSchema.pre('validate', { query: true }, function() { throw new Error('Oops!'); }); const contactSchema = Schema({ addresses: [addressSchema] }); const Contact = db.model('Test', contactSchema); const update = { addresses: [{ countryId: 'foo' }] }; const err = await Contact.updateOne( {}, update, { runValidators: true } ).then(() => null, err => err); assert.ok(err.errors['addresses.0']); assert.equal(err.errors['addresses.0'].message, 'Oops!'); }); }); it('query with top-level _bsontype (gh-8222) (gh-8268)', async function() { const userSchema = Schema({ token: String }); const User = db.model('Test', userSchema);
const original = await User.create({ token: 'rightToken' }); let doc = await User.findOne({ token: 'wrongToken', _bsontype: 'a' }); assert.ok(!doc); doc = await User.findOne(original._id); assert.ok(doc); assert.equal(doc.token, 'rightToken'); }); it('casts $elemMatch with dbrefs (gh-8577)', async function() { const ChatSchema = new Schema({ members: [{ $ref: String, $id: mongoose.ObjectId, $db: String }] }); const Chat = db.model('Test', ChatSchema);
const doc = await Chat.create({ members: [{ $ref: 'foo', $id: new mongoose.Types.ObjectId(), $db: 'foo' }] }); const res = await Chat.findOne({ members: { $elemMatch: { $id: doc.members[0].$id } } }); assert.ok(res); }); it('throws an error if executed multiple times (gh-7398)', async function() { const Test = db.model('Test', Schema({ name: String }));
const q = Test.findOne(); await q; let err = await q.then(() => null, err => err); assert.ok(err); assert.equal(err.name, 'MongooseError'); assert.equal(err.message, 'Query was already executed: Test.findOne({})'); assert.ok(err.originalStack); const cb = () => {}; err = await Test.find(cb).then(() => null, err => err); assert.ok(err); assert.equal(err.name, 'MongooseError'); assert.equal(err.message, 'Query was already executed: Test.find({})'); assert.ok(err.originalStack); err = await q.clone().then(() => null, err => err); assert.ifError(err); }); describe('stack traces', function() { it('includes calling file for filter cast errors (gh-8691)', async function() { const toCheck = ['find', 'findOne', 'deleteOne']; const Model = db.model('Test', Schema({}));
for (const fn of toCheck) { const err = await Model[fn]({ _id: 'fail' }).then(() => null, err => err); assert.ok(err); assert.ok(err.stack.includes(__filename), err.stack); } }); }); it('setter priorVal (gh-8629)', function() { const priorVals = []; const schema = Schema({ name: { type: String, set: (v, priorVal) => { priorVals.push(priorVal); return v; } } }); const Model = db.model('Test', schema); return Model.updateOne({}, { name: 'bar' }).exec(). then(() => assert.deepEqual(priorVals, [null])); }); describe('clone', function() { let Model; beforeEach(function() { const schema = new Schema({ name: String, age: Number }); Model = db.model('Test', schema); return Model.create([ { name: 'Jean-Luc Picard', age: 59 }, { name: 'Will Riker', age: 29 } ]); }); it('with findOne', async function() { const q = Model.findOne({ age: 29 }); const q2 = q.clone(); let doc = await q; assert.equal(doc.name, 'Will Riker'); assert.deepEqual(q2._conditions, { age: 29 }); doc = await q2; assert.equal(doc.name, 'Will Riker'); const q3 = q.clone(); assert.deepEqual(q3._conditions, { age: 29 }); doc = await q3; assert.equal(doc.name, 'Will Riker'); }); it('with deleteOne', async function() { const q = Model.deleteOne({ age: 29 }); await q; assert.equal(await Model.findOne({ name: 'Will Riker' }), null); await Model.create({ name: 'Will Riker', age: 29 }); const q2 = q.clone(); assert.deepEqual(q2._conditions, { age: 29 }); await q2; assert.equal(await Model.findOne({ name: 'Will Riker' }), null); }); it('with updateOne', async function() { const q = Model.updateOne({ name: 'Will Riker' }, { name: 'Thomas Riker' }); await q; assert.equal(await Model.findOne({ name: 'Will Riker' }), null); await Model.updateOne({ name: 'Thomas Riker' }, { name: 'Will Riker' }); const q2 = q.clone(); assert.deepEqual(q2._conditions, { name: 'Will Riker' }); assert.deepEqual(q2._update, { $set: { name: 'Thomas Riker' } }); await q2; assert.equal(await Model.findOne({ name: 'Will Riker' }), null); }); it('with distinct', async function() { const q = Model.distinct('name'); const res = await q; assert.deepEqual(res.sort(), ['Jean-Luc Picard', 'Will Riker']); const q2 = q.clone(); assert.deepEqual(q2._distinct, 'name'); await q2; assert.deepEqual(res.sort(), ['Jean-Luc Picard', 'Will Riker']); }); it('with hooks (gh-12365)', async function() { db.deleteModel('Test'); const schema = new Schema({ name: String, age: Number }); let called = 0; schema.pre('find', () => ++called); Model = db.model('Test', schema); assert.strictEqual(called, 0); const res = await Model.find().clone(); assert.strictEqual(called, 1); assert.equal(res.length, 2); assert.deepEqual(res.map(doc => doc.name).sort(), ['Jean-Luc Picard', 'Will Riker']); }); }); it('casts filter according to discriminator schema if in filter (gh-8881)', async function() { const userSchema = new Schema({ name: String }, { discriminatorKey: 'kind' }); const User = db.model('User', userSchema);
const UserWithAge = User.discriminator('UserWithAge', new Schema({ age: Number })); await UserWithAge.create({ name: 'Hafez', age: 25 }); // should cast `age` to number const user = await User.findOne({ kind: 'UserWithAge', age: '25' }); assert.equal(user.name, 'Hafez'); assert.equal(user.age, 25); }); it('casts update object according to child discriminator schema when `discriminatorKey` is present (gh-8982)', async function() { const userSchema = new Schema({}, { discriminatorKey: 'kind' }); const Person = db.model('Person', userSchema);
const Worker = Person.discriminator('Worker', new Schema({ locations: [String] })); const worker = await Worker.create({ locations: ['US'] }); // should cast `update` according to `Worker` schema await Person.updateOne({ _id: worker._id, kind: 'Worker' }, { $push: { locations: 'UK' } }); const person = await Person.findOne({ _id: worker._id }); assert.deepEqual(person.locations, ['US', 'UK']); }); it('allows disabling `setDefaultsOnInsert` (gh-8410)', function() { const schema = new Schema({ title: String, genre: { type: String, default: 'Action' } }); const Movie = db.model('Movie', schema); const query = {}; const update = { title: 'The Terminator' }; const options = { new: true, upsert: true, setDefaultsOnInsert: false, lean: true }; return Movie.deleteMany({}). then(() => Movie.findOneAndUpdate(query, update, options)). then(doc => { assert.strictEqual(doc.genre, void 0); }); }); it('throws readable error if `$and` and `$or` contain non-objects (gh-8948)', async function() { const userSchema = new Schema({ name: String }); const Person = db.model('Person', userSchema);
let err = await Person.find({ $and: [null] }).catch(err => err); assert.equal(err.name, 'CastError'); assert.equal(err.path, '$and.0'); err = await Person.find({ $or: [false] }).catch(err => err); assert.equal(err.name, 'CastError'); assert.equal(err.path, '$or.0'); err = await Person.find({ $nor: ['not an object'] }).catch(err => err); assert.equal(err.name, 'CastError'); assert.equal(err.path, '$nor.0'); }); it('includes `undefined` in filters (gh-3944)', async function() { const userSchema = new Schema({ name: String, pwd: String }); const Person = db.model('Person', userSchema);
await Person.create([ { name: 'test1', pwd: 'my secret' }, { name: 'test2', pwd: null } ]); let res = await Person.findOne({ name: 'test1', pwd: void 0 }); assert.equal(res, null); res = await Person.findOne({ name: 'test2', pwd: { $eq: void 0 } }); assert.equal(res.name, 'test2'); }); it('handles push with array filters (gh-9977)', async function() { const questionSchema = new Schema({ question_type: { type: String, enum: ['mcq', 'essay'] } }, { discriminatorKey: 'question_type', strict: 'throw' }); const quizSchema = new Schema({ quiz_title: String, questions: [questionSchema] }, { strict: 'throw' }); const Quiz = db.model('Test', quizSchema); const mcqQuestionSchema = new Schema({ text: String, choices: [{ choice_text: String, is_correct: Boolean }] }, { strict: 'throw' }); quizSchema.path('questions').discriminator('mcq', mcqQuestionSchema); const id1 = new mongoose.Types.ObjectId(); const id2 = new mongoose.Types.ObjectId();
let quiz = await Quiz.create({ quiz_title: 'quiz', questions: [ { _id: id1, question_type: 'mcq', text: 'A or B?', choices: [ { choice_text: 'A', is_correct: false }, { choice_text: 'B', is_correct: true } ] }, { _id: id2, question_type: 'mcq' } ] }); const filter = { questions: { $elemMatch: { _id: id2, question_type: 'mcq' } } }; await Quiz.updateOne(filter, { $push: { 'questions.$.choices': { choice_text: 'choice 1', is_correct: false } } }); quiz = await Quiz.findById(quiz); assert.equal(quiz.questions[1].choices.length, 1); assert.equal(quiz.questions[1].choices[0].choice_text, 'choice 1'); await Quiz.updateOne({ questions: { $elemMatch: { _id: id2 } } }, { $push: { 'questions.$[q].choices': { choice_text: 'choice 3', is_correct: false } } }, { arrayFilters: [{ 'q.question_type': 'mcq' }] }); quiz = await Quiz.findById(quiz); assert.equal(quiz.questions[1].choices.length, 2); assert.equal(quiz.questions[1].choices[1].choice_text, 'choice 3'); }); it('Query#pre() (gh-9784)', async function() { const Question = db.model('Test', Schema({ answer: Number })); const q1 = Question.find({ answer: 42 }); const called = []; q1.pre(function middleware() { called.push(this.getFilter()); }); await q1.exec(); assert.equal(called.length, 1); assert.deepEqual(called[0], { answer: 42 }); await Question.find({ answer: 42 }); assert.equal(called.length, 1); }); it('applies schema-level `select` on arrays (gh-10029)', function() { const testSchema = new mongoose.Schema({ doesntpopulate: { type: [mongoose.Schema.Types.ObjectId], select: false }, populatescorrectly: [{ type: mongoose.Schema.Types.ObjectId, select: false }] }); const Test = db.model('Test', testSchema); const q = Test.find(); q._applyPaths(); assert.deepEqual(q._fields, { doesntpopulate: 0, populatescorrectly: 0 }); }); it('sets `writeConcern` option correctly (gh-10009)', function() { const testSchema = new mongoose.Schema({ name: String }); const Test = db.model('Test', testSchema); const q = Test.find(); q.writeConcern({ w: 'majority', wtimeout: 1000 }); assert.deepEqual(q.options.writeConcern, { w: 'majority', wtimeout: 1000 }); }); it('no longer has the deprecation warning message with writeConcern gh-10083', async function() { const MySchema = new mongoose.Schema( { _id: { type: Number, required: true }, op: { type: String, required: true }, size: { type: Number, required: true }, totalSize: { type: Number, required: true } }, { versionKey: false, writeConcern: { w: 'majority', j: true, wtimeout: 15000 } } ); const Test = db.model('Test', MySchema); // pops up on creation of model const entry = await Test.create({ _id: 12345678, op: 'help', size: 54, totalSize: 104 }); await entry.save(); }); it('sanitizeProjection option (gh-10243)', function() { const MySchema = Schema({ name: String, email: String }); const Test = db.model('Test', MySchema); let q = Test.find().select({ email: '$name' }); assert.deepEqual(q._fields, { email: '$name' }); q = Test.find().setOptions({ sanitizeProjection: true }).select({ email: '$name' }); assert.deepEqual(q._fields, { email: 1 }); q = Test.find().select({ email: '$name' }).setOptions({ sanitizeProjection: true }); assert.deepEqual(q._fields, { email: 1 }); }); it('sanitizeFilter option (gh-3944)', function() { const MySchema = Schema({ username: String, pwd: String }); const Test = db.model('Test', MySchema); let q = Test.find({ username: 'val', pwd: 'my secret' }).setOptions({ sanitizeFilter: true }); q._castConditions(); assert.ifError(q.error()); assert.deepEqual(q._conditions, { username: 'val', pwd: 'my secret' }); q = Test.find({ username: 'val', pwd: { $ne: null } }).setOptions({ sanitizeFilter: true }); q._castConditions(); assert.ok(q.error()); assert.equal(q.error().name, 'CastError'); q = Test.find({ username: 'val', pwd: mongoose.trusted({ $gt: null }) }). setOptions({ sanitizeFilter: true }); q._castConditions(); assert.ifError(q.error()); assert.deepEqual(q._conditions, { username: 'val', pwd: { $gt: null } }); }); it('should not error when $not is used with $size (gh-10716)', async function() { const barSchema = Schema({ bar: String }); const testSchema = Schema({ foo: String, bars: [barSchema] }); const Test = db.model('Zulu', testSchema); const entry = await Test.create({ foo: 'hello', bars: [{ bar: 'world' }, { bar: 'world1' }] }); await entry.save(); const foos = await Test.find({ bars: { $not: { $size: 0 } } }); assert.ok(foos); }); it('should not error when $not is used on an array of strings (gh-11467)', async function() { const testSchema = Schema({ names: [String] }); const Test = db.model('Test', testSchema); await Test.create([{ names: ['foo'] }, { names: ['bar'] }]); let res = await Test.find({ names: { $not: /foo/ } }); assert.deepStrictEqual(res.map(el => el.names), [['bar']]); // MongoDB server < 4.4 doesn't support `{ $not: { $regex } }`, see: // https://github.com/Automattic/mongoose/runs/5441062834?check_suite_focus=true const version = await start.mongodVersion(); if (version[0] < 4 || (version[0] === 4 && version[1] < 4)) { return; } res = await Test.find({ names: { $not: { $regex: 'foo' } } }); assert.deepStrictEqual(res.map(el => el.names), [['bar']]); }); it('adding `exec` option does not affect the query (gh-11416)', async() => { const userSchema = new Schema({ name: { type: String } });
const User = db.model('User', userSchema); const createdUser = await User.create({ name: 'Hafez' }); const users = await User.find({ _id: createdUser._id }).setOptions({ exec: false }); assert.ok(users.length, 1); }); it('handles queries with EJSON deserialized RegExps (gh-11597)', async function() { const testSchema = new mongoose.Schema({ name: String }); const Test = db.model('Test', testSchema); await Test.create({ name: '@foo.com' }); await Test.create({ name: 'adfadfasdf' }); const result = await Test.find( EJSON.deserialize({ name: { $regex: '@foo.com', $options: 'i' } }) ); assert.equal(result.length, 1); assert.equal(result[0].name, '@foo.com'); }); it('should return query helper supplied in schema options query property instead of undefined', function(done) { const Model = db.model('Test', new Schema({ userName: { type: String, required: [true, 'userName is required'] } }, { query: { byUserName(userName) { return this.where({ userName }); } } })); Model.create({ userName: 'test' }, function(error) { if (error instanceof Error) { return done(error); } Model.find().byUserName('test').exec(function(error, docs) { if (error instanceof Error) { return done(error); } assert.equal(docs.length, 1); assert.equal(docs[0].userName, 'test'); done(); }); }); }); it('allows a transform option for lean on a query (gh-10423)', async function() { const arraySchema = new mongoose.Schema({ sub: String }); const subDoc = new mongoose.Schema({ nickName: String }); const testSchema = new mongoose.Schema({ name: String, foo: [arraySchema], otherName: subDoc }); const Test = db.model('Test', testSchema); await Test.create({ name: 'foo', foo: [{ sub: 'Test' }, { sub: 'Testerson' }], otherName: { nickName: 'Bar' } }); const result = await Test.find().lean({ transform: (doc) => { delete doc._id; return doc; } }); assert.strictEqual(result[0]._id, undefined); assert.strictEqual(result[0].otherName._id, undefined); assert.strictEqual(result[0].foo[0]._id, undefined); assert.strictEqual(result[0].foo[1]._id, undefined); const single = await Test.findOne().lean({ transform: (doc) => { delete doc._id; return doc; } }); assert.strictEqual(single._id, undefined); assert.strictEqual(single.otherName._id, undefined); assert.strictEqual(single.foo[0]._id, undefined); assert.strictEqual(single.foo[0]._id, undefined); }); it('handles a lean transform that deletes _id with populate (gh-12143) (gh-10423)', async function() { const testSchema = Schema({ name: String, user: { type: mongoose.Types.ObjectId, ref: 'User' } }); const userSchema = Schema({ name: String }); const Test = db.model('Test', testSchema); const User = db.model('User', userSchema); const user = await User.create({ name: 'John Smith' }); let test = await Test.create({ name: 'test', user }); test = await Test.findById(test).populate('user').lean({ transform: (doc) => { delete doc._id; delete doc.__v; return doc; } }); assert.ok(test); assert.deepStrictEqual(test, { name: 'test', user: { name: 'John Smith' } }); }); it('skips applying default projections over slice projections (gh-11940)', async function() { const commentSchema = new mongoose.Schema({ comment: String }); const testSchema = new mongoose.Schema({ name: String, comments: { type: [commentSchema], select: false } }); const Test = db.model('Test', testSchema); const { _id } = await Test.create({ name: 'Test', comments: [{ comment: 'test1' }, { comment: 'test2' }] }); const doc = await Test.findById(_id).slice('comments', [1, 1]); assert.equal(doc.comments.length, 1); assert.equal(doc.comments[0].comment, 'test2'); }); describe('set()', function() { it('overwrites top-level keys if setting to undefined (gh-12155)', function() { const testSchema = new mongoose.Schema({ key: String, prop: String }); const Test = db.model('Test', testSchema); const query = Test.findOneAndUpdate({}, { key: '', prop: 'foo' }); query.set('key', undefined); const update = query.getUpdate(); assert.deepEqual(update, { $set: { key: undefined }, prop: 'foo' }); }); }); it('select: false is ignored for type Map (gh-12445)', async function() { const testSchema = new mongoose.Schema({ select: { type: Map, of: Object }, doNotSelect: { type: Map, of: Object, select: false } }); const Test = db.model('Test', testSchema); await Test.create({ select: { key: { some: 'value' } }, doNotSelect: { otherKey: { someOther: 'value' } } }); const item = await Test.findOne(); assert.equal(item.get('select.key.some'), 'value'); assert.equal(item.doNotSelect, undefined); }); it('Map field with select: false is selected when explicitly requested (gh-12603)', async function() { const testSchema = new mongoose.Schema({ title: String, body: { type: Map, of: { en: String, pt: String }, select: false } }); const Test = db.model('Test', testSchema); await Test.create({ title: 'test', body: { A: { en: 'en test A value', pt: 'pt test A value' }, B: { en: 'en test B value', pt: 'pt test B value' } } }); const item = await Test.findOne({}).select('+body'); assert.equal(item.title, 'test'); assert.equal(item.get('body.A.en'), 'en test A value'); const item2 = await Test.findOne({}).select('body'); assert.equal(item2.title, undefined); assert.equal(item2.get('body.A.en'), 'en test A value'); }); it('treats ObjectId as object with `_id` for `merge()` (gh-12325)', async function() { const testSchema = new mongoose.Schema({ name: String }); const Test = db.model('Test', testSchema); const _id = new mongoose.Types.ObjectId(); let q = Test.find(_id); assert.ok(q.getFilter()._id instanceof mongoose.Types.ObjectId); assert.equal(q.getFilter()._id.toHexString(), _id.toHexString()); q = Test.findOne(_id); assert.ok(q.getFilter()._id instanceof mongoose.Types.ObjectId); assert.equal(q.getFilter()._id.toHexString(), _id.toHexString()); }); it('avoid throwing error when modifying nested field with same name as discriminator key (gh-12517)', async function() { const options = { discriminatorKey: 'kind', strict: 'throw' }; const testSchema = new mongoose.Schema({ name: String, kind: String, animals: { kind: String, world: String } }, options); const Test = db.model('Test', testSchema); Test.discriminator( 'ClickedTest', new mongoose.Schema({ url: String }, options) ); const newItem = await Test.create({ name: 'Name', animals: { kind: 'Kind', world: 'World' } }); const updatedItem = await Test.findByIdAndUpdate( newItem._id, { $set: { name: 'Name2', animals: { kind: 'Kind2', world: 'World2' } } }, { new: true } ); assert.deepEqual(updatedItem.animals, { kind: 'Kind2', world: 'World2' }); await assert.rejects(async() => { await Test.findByIdAndUpdate( newItem._id, { $set: { name: 'Name2', kind: 'Kind2' } } ); }, { message: 'Can\'t modify discriminator key "kind" on discriminator model' }); }); it('avoid throwing error when modifying field with same name as nested discriminator key (gh-12517)', async function() { const options = { discriminatorKey: 'animals.kind', strict: 'throw' }; const testSchema = new mongoose.Schema({ name: String, kind: String, animals: { kind: String, world: String } }, options); const Test = db.model('Test', testSchema); Test.discriminator( 'ClickedTest', new mongoose.Schema({ url: String }, options) ); const newItem = await Test.create({ name: 'Name', kind: 'Kind', animals: { world: 'World' } }); const updatedItem = await Test.findByIdAndUpdate( newItem._id, { $set: { name: 'Name2', kind: 'Kind2' } }, { new: true } ); assert.equal(updatedItem.name, 'Name2'); assert.equal(updatedItem.kind, 'Kind2'); await assert.rejects(async() => { await Test.findByIdAndUpdate( newItem._id, { $set: { animals: { kind: 'Kind2', world: 'World2' } } } ); }, { message: 'Can\'t modify discriminator key "animals.kind" on discriminator model' }); }); it('global strictQuery should work if applied after schema creation (gh-12703)', async() => { const m = new mongoose.Mongoose(); await m.connect(start.uri); const schema = new mongoose.Schema({ title: String }); const Test = m.model('test', schema); m.set('strictQuery', false); await Test.create({ title: 'chimichanga' }); await Test.create({ title: 'burrito bowl' }); await Test.create({ title: 'taco supreme' }); const cond = { $or: [ { title: { $regex: 'urri', $options: 'i' } }, { name: { $regex: 'urri', $options: 'i' } } ] }; const found = await Test.find(cond); assert.strictEqual(found.length, 1); assert.strictEqual(found[0].title, 'burrito bowl'); });});