deno.land / x / mongoose@6.7.5 / test / document.test.js
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030'use strict';
/** * Module dependencies. */const start = require('./common');
const Document = require('../lib/document');const EventEmitter = require('events').EventEmitter;const ArraySubdocument = require('../lib/types/ArraySubdocument');const Query = require('../lib/query');const assert = require('assert');const idGetter = require('../lib/helpers/schema/idGetter');const util = require('./util');const utils = require('../lib/utils');
const mongoose = start.mongoose;const Schema = mongoose.Schema;const ObjectId = Schema.ObjectId;const DocumentObjectId = mongoose.Types.ObjectId;const SchemaType = mongoose.SchemaType;const ValidatorError = SchemaType.ValidatorError;const ValidationError = mongoose.Document.ValidationError;const VersionError = mongoose.Error.VersionError;const MongooseError = mongoose.Error;const DocumentNotFoundError = mongoose.Error.DocumentNotFoundError;
/** * Test Document constructor. */function TestDocument() { Document.apply(this, arguments);}
/** * Inherits from Document. */Object.setPrototypeOf(TestDocument.prototype, Document.prototype);
for (const i in EventEmitter.prototype) { TestDocument[i] = EventEmitter.prototype[i];}
/** * Set a dummy schema to simulate compilation. */const em = new Schema({ title: String, body: String });em.virtual('works').get(function() { return 'em virtual works';});const schema = new Schema({ test: String, oids: [ObjectId], numbers: [Number], nested: { age: Number, cool: ObjectId, deep: { x: String }, path: String, setr: String }, nested2: { nested: String, yup: { nested: Boolean, yup: String, age: Number } }, em: [em], date: Date});
TestDocument.prototype.$__setSchema(idGetter(schema));
schema.virtual('nested.agePlus2').get(function() { return this.nested.age + 2;});schema.virtual('nested.setAge').set(function(v) { this.nested.age = v;});schema.path('nested.path').get(function(v) { return (this.nested.age || '') + (v ? v : '');});schema.path('nested.setr').set(function(v) { return v + ' setter';});
let dateSetterCalled = false;schema.path('date').set(function(v) { // should not have been cast to a Date yet if (v !== undefined) { assert.equal(typeof v, 'string'); } dateSetterCalled = true; return v;});
/** * Method subject to hooks. Simply fires the callback once the hooks are * executed. */TestDocument.prototype.hooksTest = function(fn) { fn(null, arguments);};
const childSchema = new Schema({ counter: Number });
const parentSchema = new Schema({ name: String, children: [childSchema]});
/** * Test. */describe('document', function() { let db; before(function() { db = start(); }); after(async function() { await db.close(); }); beforeEach(() => db.deleteModel(/.*/)); afterEach(() => util.clearTestData(db)); afterEach(() => util.stopRemainingOps(db)); describe('constructor', function() { it('supports passing in schema directly (gh-8237)', function() { const myUserDoc = new Document({}, { name: String }); assert.ok(!myUserDoc.name); myUserDoc.name = 123; assert.strictEqual(myUserDoc.name, '123'); assert.ifError(myUserDoc.validateSync()); }); }); describe('delete', function() { it('deletes the document', async function() { const schema = new Schema({ x: String }); const Test = db.model('Test', schema); const test = new Test({ x: 'test' }); const doc = await test.save(); await doc.delete(); const found = await Test.findOne({ _id: doc._id }); assert.strictEqual(found, null); }); }); describe('updateOne', function() { let Test; before(function() { const schema = new Schema({ x: String, y: String }); db.deleteModel(/^Test$/); Test = db.model('Test', schema); }); it('updates the document', async function() { const test = new Test({ x: 'test' }); const doc = await test.save(); await doc.updateOne({ y: 'test' }); const found = await Test.findOne({ _id: doc._id }); assert.strictEqual(found.y, 'test'); }); it('returns a query', function() { const doc = new Test({ x: 'test' }); assert.ok(doc.updateOne() instanceof Test.Query); }); it('middleware (gh-8262)', async function() { const schema = new Schema({ x: String, y: String }); const docs = []; schema.post('updateOne', { document: true, query: false }, function(doc, next) { docs.push(doc); next(); }); const Model = db.model('Test', schema);
const doc = await Model.create({ x: 2, y: 4 }); await doc.updateOne({ x: 4 }); assert.equal(docs.length, 1); assert.equal(docs[0], doc); }); }); describe('replaceOne', function() { it('replaces the document', async function() { const schema = new Schema({ x: String }); const Test = db.model('Test', schema); const test = new Test({ x: 'test' }); const doc = await test.save(); await doc.replaceOne({ x: 'updated' }); const found = await Test.findOne({ _id: doc._id }); assert.strictEqual(found.x, 'updated'); }); }); describe('shortcut getters', function() { it('return undefined for properties with a null/undefined parent object (gh-1326)', function() { const doc = new TestDocument(); doc.init({ nested: null }); assert.strictEqual(undefined, doc.nested.age); }); it('work', function() { const doc = new TestDocument(); doc.init({ test: 'test', oids: [], nested: { age: 5, cool: DocumentObjectId.createFromHexString('4c6c2d6240ced95d0e00003c'), path: 'my path' } }); assert.equal(doc.test, 'test'); assert.ok(doc.oids instanceof Array); assert.equal(doc.nested.age, 5); assert.equal(String(doc.nested.cool), '4c6c2d6240ced95d0e00003c'); assert.equal(doc.nested.agePlus2, 7); assert.equal(doc.nested.path, '5my path'); doc.nested.setAge = 10; assert.equal(doc.nested.age, 10); doc.nested.setr = 'set it'; assert.equal(doc.$__getValue('nested.setr'), 'set it setter'); const doc2 = new TestDocument(); doc2.init({ test: 'toop', oids: [], nested: { age: 2, cool: DocumentObjectId.createFromHexString('4cf70857337498f95900001c'), deep: { x: 'yay' } } }); assert.equal(doc2.test, 'toop'); assert.ok(doc2.oids instanceof Array); assert.equal(doc2.nested.age, 2); // GH-366 assert.equal(doc2.nested.bonk, undefined); assert.equal(doc2.nested.nested, undefined); assert.equal(doc2.nested.test, undefined); assert.equal(doc2.nested.age.test, undefined); assert.equal(doc2.nested.age.nested, undefined); assert.equal(doc2.oids.nested, undefined); assert.equal(doc2.nested.deep.x, 'yay'); assert.equal(doc2.nested.deep.nested, undefined); assert.equal(doc2.nested.deep.cool, undefined); assert.equal(doc2.nested2.yup.nested, undefined); assert.equal(doc2.nested2.yup.nested2, undefined); assert.equal(doc2.nested2.yup.yup, undefined); assert.equal(doc2.nested2.yup.age, undefined); assert.equal(typeof doc2.nested2.yup, 'object'); doc2.nested2.yup = { age: 150, yup: 'Yesiree', nested: true }; assert.equal(doc2.nested2.nested, undefined); assert.equal(doc2.nested2.yup.nested, true); assert.equal(doc2.nested2.yup.yup, 'Yesiree'); assert.equal(doc2.nested2.yup.age, 150); doc2.nested2.nested = 'y'; assert.equal(doc2.nested2.nested, 'y'); assert.equal(doc2.nested2.yup.nested, true); assert.equal(doc2.nested2.yup.yup, 'Yesiree'); assert.equal(doc2.nested2.yup.age, 150); assert.equal(String(doc2.nested.cool), '4cf70857337498f95900001c'); assert.ok(doc.oids !== doc2.oids); }); }); it('test shortcut setters', function() { const doc = new TestDocument(); doc.init({ test: 'Test', nested: { age: 5 } }); assert.equal(doc.isModified('test'), false); doc.test = 'Woot'; assert.equal(doc.test, 'Woot'); assert.equal(doc.isModified('test'), true); assert.equal(doc.isModified('nested.age'), false); doc.nested.age = 2; assert.equal(doc.nested.age, 2); assert.ok(doc.isModified('nested.age')); doc.nested = { path: 'overwrite the entire nested object' }; assert.equal(doc.nested.age, undefined); assert.equal(Object.keys(doc._doc.nested).length, 1); assert.equal(doc.nested.path, 'overwrite the entire nested object'); assert.ok(doc.isModified('nested')); }); it('test accessor of id', function() { const doc = new TestDocument(); assert.ok(doc._id instanceof DocumentObjectId); }); it('test shortcut of id hexString', function() { const doc = new TestDocument(); assert.equal(typeof doc.id, 'string'); }); it('toObject options', function() { const doc = new TestDocument(); doc.init({ test: 'test', oids: [], em: [{ title: 'asdf' }], nested: { age: 5, cool: DocumentObjectId.createFromHexString('4c6c2d6240ced95d0e00003c'), path: 'my path' }, nested2: {}, date: new Date() }); let clone = doc.toObject({ getters: true, virtuals: false }); assert.equal(clone.test, 'test'); assert.ok(clone.oids instanceof Array); assert.equal(clone.nested.age, 5); assert.equal(clone.nested.cool.toString(), '4c6c2d6240ced95d0e00003c'); assert.equal(clone.nested.path, '5my path'); assert.equal(clone.nested.agePlus2, undefined); assert.equal(clone.em[0].works, undefined); assert.ok(clone.date instanceof Date); clone = doc.toObject({ virtuals: true }); assert.equal(clone.test, 'test'); assert.ok(clone.oids instanceof Array); assert.equal(clone.nested.age, 5); assert.equal(clone.nested.cool.toString(), '4c6c2d6240ced95d0e00003c'); assert.equal(clone.nested.path, 'my path'); assert.equal(clone.nested.agePlus2, 7); assert.equal(clone.em[0].works, 'em virtual works'); clone = doc.toObject({ getters: true }); assert.equal(clone.test, 'test'); assert.ok(clone.oids instanceof Array); assert.equal(clone.nested.age, 5); assert.equal(clone.nested.cool.toString(), '4c6c2d6240ced95d0e00003c'); assert.equal(clone.nested.path, '5my path'); assert.equal(clone.nested.agePlus2, 7); assert.equal(clone.em[0].works, 'em virtual works'); // test toObject options doc.schema.options.toObject = { virtuals: true }; clone = doc.toObject({ transform: false, virtuals: true }); assert.equal(clone.test, 'test'); assert.ok(clone.oids instanceof Array); assert.equal(clone.nested.age, 5); assert.equal(clone.nested.cool.toString(), '4c6c2d6240ced95d0e00003c'); assert.equal(clone.nested.path, 'my path'); assert.equal(clone.nested.agePlus2, 7); assert.equal(clone.em[0].title, 'asdf'); delete doc.schema.options.toObject; // minimize clone = doc.toObject({ minimize: true }); assert.equal(clone.nested2, undefined); clone = doc.toObject({ minimize: true, getters: true }); assert.equal(clone.nested2, undefined); clone = doc.toObject({ minimize: false }); assert.equal(clone.nested2.constructor.name, 'Object'); assert.equal(Object.keys(clone.nested2).length, 1); clone = doc.toObject('2'); assert.equal(clone.nested2, undefined); doc.schema.options.toObject = { minimize: false }; clone = doc.toObject({ transform: false, minimize: false }); assert.equal(clone.nested2.constructor.name, 'Object'); assert.equal(Object.keys(clone.nested2).length, 1); delete doc.schema.options.toObject; doc.schema.options.minimize = false; clone = doc.toObject(); assert.equal(clone.nested2.constructor.name, 'Object'); assert.equal(Object.keys(clone.nested2).length, 1); doc.schema.options.minimize = true; clone = doc.toObject(); assert.equal(clone.nested2, undefined); // transform doc.schema.options.toObject = {}; doc.schema.options.toObject.transform = function xform(doc, ret) { // ignore embedded docs if (doc.$isSubdocument) { return; } delete ret.em; delete ret.numbers; delete ret.oids; ret._id = ret._id.toString(); }; clone = doc.toObject(); assert.equal(doc.id, clone._id); assert.ok(undefined === clone.em); assert.ok(undefined === clone.numbers); assert.ok(undefined === clone.oids); assert.equal(clone.test, 'test'); assert.equal(clone.nested.age, 5); // transform with return value const out = { myid: doc._id.toString() }; doc.schema.options.toObject.transform = function(doc, ret) { // ignore embedded docs if (doc.$isSubdocument) { return; } return { myid: ret._id.toString() }; }; clone = doc.toObject(); assert.deepEqual(out, clone); // ignored transform with inline options clone = doc.toObject({ x: 1, transform: false }); assert.ok(!('myid' in clone)); assert.equal(clone.test, 'test'); assert.ok(clone.oids instanceof Array); assert.equal(clone.nested.age, 5); assert.equal(clone.nested.cool.toString(), '4c6c2d6240ced95d0e00003c'); assert.equal(clone.nested.path, 'my path'); assert.equal(clone.em[0].constructor.name, 'Object'); // applied transform when inline transform is true clone = doc.toObject({ x: 1 }); assert.deepEqual(out, clone); // transform passed inline function xform(self, doc, opts) { opts.fields.split(' ').forEach(function(field) { delete doc[field]; }); } clone = doc.toObject({ transform: xform, fields: '_id em numbers oids nested' }); assert.equal(doc.test, 'test'); assert.ok(undefined === clone.em); assert.ok(undefined === clone.numbers); assert.ok(undefined === clone.oids); assert.ok(undefined === clone._id); assert.ok(undefined === clone.nested); // all done delete doc.schema.options.toObject; }); it('toObject transform', async function() { const schema = new Schema({ name: String, places: [{ type: ObjectId, ref: 'Place' }] }); const schemaPlaces = new Schema({ identity: String }); schemaPlaces.set('toObject', { transform: function(doc, ret) { assert.equal(doc.constructor.modelName, 'Place'); return ret; } }); const Test = db.model('Test', schema); const Places = db.model('Place', schemaPlaces); const [a, b, c] = await Places.create({ identity: 'a' }, { identity: 'b' }, { identity: 'c' }); await Test.create({ name: 'chetverikov', places: [a, b, c] }); const docs = await Test.findOne({}).populate('places').exec(); docs.toObject({ transform: true }); }); it('disabling aliases in toObject options (gh-7548)', function() { const schema = new mongoose.Schema({ name: { type: String, alias: 'nameAlias' }, age: Number }); schema.virtual('answer').get(() => 42); const Model = db.model('Person', schema); const doc = new Model({ name: 'Jean-Luc Picard', age: 59 }); let obj = doc.toObject({ virtuals: true }); assert.equal(obj.nameAlias, 'Jean-Luc Picard'); assert.equal(obj.answer, 42); obj = doc.toObject({ virtuals: true, aliases: false }); assert.ok(!obj.nameAlias); assert.equal(obj.answer, 42); }); it('can save multiple times with changes to complex subdocuments (gh-8531)', () => { const clipSchema = Schema({ height: Number, rows: Number, width: Number }, { _id: false, id: false }); const questionSchema = Schema({ type: String, age: Number, clip: { type: clipSchema } }, { _id: false, id: false }); const keySchema = Schema({ ql: [questionSchema] }, { _id: false, id: false }); const Model = db.model('Test', Schema({ name: String, keys: [keySchema] })); const doc = new Model({ name: 'test', keys: [ { ql: [ { type: 'mc', clip: { width: 1 } }, { type: 'mc', clip: { height: 1, rows: 1 } }, { type: 'mc', clip: { height: 2, rows: 1 } }, { type: 'mc', clip: { height: 3, rows: 1 } } ] } ] }); return doc.save().then(() => { // The following was failing before fixing gh-8531 because // the validation was called for the "clip" document twice in the // same stack, causing a "can't validate() the same doc multiple times in // parallel" warning doc.keys[0].ql[0].clip = { width: 4.3, rows: 3 }; doc.keys[0].ql[0].age = 42; return doc.save(); }); // passes }); it('saves even if `_id` is null (gh-6406)', async function() { const schema = new Schema({ _id: Number, val: String }); const Model = db.model('Test', schema);
await Model.updateOne({ _id: null }, { val: 'test' }, { upsert: true }); let doc = await Model.findOne(); doc.val = 'test2'; // Should not throw await doc.save(); doc = await Model.findOne(); assert.strictEqual(doc._id, null); assert.equal(doc.val, 'test2'); }); it('allows you to skip validation on save (gh-2981)', function() { const schema = new Schema({ name: { type: String, required: true } }); const MyModel = db.model('Test', schema); const doc = new MyModel(); return doc.save({ validateBeforeSave: false }); }); it('doesnt use custom toObject options on save', async function() { const schema = new Schema({ name: String, iWillNotBeDelete: Boolean, nested: { iWillNotBeDeleteToo: Boolean } }); schema.set('toObject', { transform: function(doc, ret) { delete ret.iWillNotBeDelete; delete ret.nested.iWillNotBeDeleteToo; return ret; } }); const Test = db.model('Test', schema); await Test.create({ name: 'chetverikov', iWillNotBeDelete: true, 'nested.iWillNotBeDeleteToo': true }); const doc = await Test.findOne({});
assert.equal(doc._doc.iWillNotBeDelete, true); assert.equal(doc._doc.nested.iWillNotBeDeleteToo, true); }); describe('toObject', function() { it('does not apply toObject functions of subdocuments to root document', async function() { const subdocSchema = new Schema({ test: String, wow: String }); subdocSchema.options.toObject = {}; subdocSchema.options.toObject.transform = function(doc, ret) { delete ret.wow; }; const docSchema = new Schema({ foo: String, wow: Boolean, sub: [subdocSchema] }); const Doc = db.model('Test', docSchema); const doc = await Doc.create({ foo: 'someString', wow: true, sub: [{ test: 'someOtherString', wow: 'thisIsAString' }] }); const obj = doc.toObject({ transform: function(doc, ret) { ret.phew = 'new'; } }); assert.equal(obj.phew, 'new'); assert.ok(!doc.sub.wow); }); it('handles child schema transforms', function() { const userSchema = new Schema({ name: String, email: String }); const topicSchema = new Schema({ title: String, email: String, followers: [userSchema] }); userSchema.options.toObject = { transform: function(doc, ret) { delete ret.email; } }; topicSchema.options.toObject = { transform: function(doc, ret) { ret.title = ret.title.toLowerCase(); } }; const Topic = db.model('Test', topicSchema); const topic = new Topic({ title: 'Favorite Foods', email: 'a@b.co', followers: [{ name: 'Val', email: 'val@test.co' }] }); const output = topic.toObject({ transform: true }); assert.equal(output.title, 'favorite foods'); assert.equal(output.email, 'a@b.co'); assert.equal(output.followers[0].name, 'Val'); assert.equal(output.followers[0].email, undefined); }); it('doesnt clobber child schema options when called with no params (gh-2035)', async function() { const userSchema = new Schema({ firstName: String, lastName: String, password: String }); userSchema.virtual('fullName').get(function() { return this.firstName + ' ' + this.lastName; }); userSchema.set('toObject', { virtuals: false }); const postSchema = new Schema({ owner: { type: Schema.Types.ObjectId, ref: 'User' }, content: String }); postSchema.virtual('capContent').get(function() { return this.content.toUpperCase(); }); postSchema.set('toObject', { virtuals: true }); const User = db.model('User', userSchema); const Post = db.model('BlogPost', postSchema); const user = new User({ firstName: 'Joe', lastName: 'Smith', password: 'password' }); const savedUser = await user.save(); const post = await Post.create({ owner: savedUser._id, content: 'lorem ipsum' }); const newPost = await Post.findById(post._id).populate('owner').exec(); const obj = newPost.toObject(); assert.equal(obj.owner.fullName, undefined); }); it('respects child schemas minimize (gh-9405)', function() { const postSchema = new Schema({ owner: { type: Schema.Types.ObjectId, ref: 'User' }, props: { type: Object, default: {} } }); const userSchema = new Schema({ firstName: String, props: { type: Object, default: {} } }, { minimize: false }); const User = db.model('User', userSchema); const Post = db.model('BlogPost', postSchema); const user = new User({ firstName: 'test' }); const post = new Post({ owner: user }); let obj = post.toObject(); assert.strictEqual(obj.props, void 0); assert.deepEqual(obj.owner.props, {}); obj = post.toObject({ minimize: false }); assert.deepEqual(obj.props, {}); assert.deepEqual(obj.owner.props, {}); obj = post.toObject({ minimize: true }); assert.strictEqual(obj.props, void 0); assert.strictEqual(obj.owner.props, void 0); }); it('minimizes single nested subdocs (gh-11247)', async function() { const nestedSchema = Schema({ bar: String }, { _id: false }); const schema = Schema({ foo: nestedSchema }); const MyModel = db.model('Test', schema); const myModel = await MyModel.create({ foo: {} }); assert.strictEqual(myModel.toObject().foo, void 0); }); }); describe('toJSON', function() { it('toJSON options', function() { const doc = new TestDocument(); doc.init({ test: 'test', oids: [], em: [{ title: 'asdf' }], nested: { age: 5, cool: DocumentObjectId.createFromHexString('4c6c2d6240ced95d0e00003c'), path: 'my path' }, nested2: {} }); // override to check if toJSON gets fired const path = TestDocument.prototype.schema.path('em'); path.casterConstructor.prototype.toJSON = function() { return {}; }; doc.schema.options.toJSON = { virtuals: true }; let clone = doc.toJSON(); assert.equal(clone.test, 'test'); assert.ok(clone.oids instanceof Array); assert.equal(clone.nested.age, 5); assert.equal(clone.nested.cool.toString(), '4c6c2d6240ced95d0e00003c'); assert.equal(clone.nested.path, 'my path'); assert.equal(clone.nested.agePlus2, 7); assert.equal(clone.em[0].constructor.name, 'Object'); assert.equal(Object.keys(clone.em[0]).length, 0); delete doc.schema.options.toJSON; delete path.casterConstructor.prototype.toJSON; doc.schema.options.toJSON = { minimize: false }; clone = doc.toJSON(); assert.equal(clone.nested2.constructor.name, 'Object'); assert.equal(Object.keys(clone.nested2).length, 1); clone = doc.toJSON('8'); assert.equal(clone.nested2.constructor.name, 'Object'); assert.equal(Object.keys(clone.nested2).length, 1); // gh-852 const arr = [doc]; let err = false; let str; try { str = JSON.stringify(arr); } catch (_) { err = true; } assert.equal(err, false); assert.ok(/nested2/.test(str)); assert.equal(clone.nested2.constructor.name, 'Object'); assert.equal(Object.keys(clone.nested2).length, 1); // transform doc.schema.options.toJSON = {}; doc.schema.options.toJSON.transform = function xform(doc, ret) { // ignore embedded docs if (doc.$isSubdocument) { return; } delete ret.em; delete ret.numbers; delete ret.oids; ret._id = ret._id.toString(); }; clone = doc.toJSON(); assert.equal(clone._id, doc.id); assert.ok(undefined === clone.em); assert.ok(undefined === clone.numbers); assert.ok(undefined === clone.oids); assert.equal(clone.test, 'test'); assert.equal(clone.nested.age, 5); // transform with return value const out = { myid: doc._id.toString() }; doc.schema.options.toJSON.transform = function(doc, ret) { // ignore embedded docs if (doc.$isSubdocument) { return; } return { myid: ret._id.toString() }; }; clone = doc.toJSON(); assert.deepEqual(out, clone); // ignored transform with inline options clone = doc.toJSON({ x: 1, transform: false }); assert.ok(!('myid' in clone)); assert.equal(clone.test, 'test'); assert.ok(clone.oids instanceof Array); assert.equal(clone.nested.age, 5); assert.equal(clone.nested.cool.toString(), '4c6c2d6240ced95d0e00003c'); assert.equal(clone.nested.path, 'my path'); assert.equal(clone.em[0].constructor.name, 'Object'); // applied transform when inline transform is true clone = doc.toJSON({ x: 1 }); assert.deepEqual(out, clone); // transform passed inline function xform(self, doc, opts) { opts.fields.split(' ').forEach(function(field) { delete doc[field]; }); } clone = doc.toJSON({ transform: xform, fields: '_id em numbers oids nested' }); assert.equal(doc.test, 'test'); assert.ok(undefined === clone.em); assert.ok(undefined === clone.numbers); assert.ok(undefined === clone.oids); assert.ok(undefined === clone._id); assert.ok(undefined === clone.nested); // all done delete doc.schema.options.toJSON; }); it('jsonifying an object', function() { const doc = new TestDocument({ test: 'woot' }); const oidString = doc._id.toString(); // convert to json string const json = JSON.stringify(doc); // parse again const obj = JSON.parse(json); assert.equal(obj.test, 'woot'); assert.equal(obj._id, oidString); }); it('jsonifying an object\'s populated items works (gh-1376)', async function() { const userSchema = new Schema({ name: String }); // includes virtual path when 'toJSON' userSchema.set('toJSON', { getters: true }); userSchema.virtual('hello').get(function() { return 'Hello, ' + this.name; }); const User = db.model('User', userSchema); const groupSchema = new Schema({ name: String, _users: [{ type: Schema.ObjectId, ref: 'User' }] }); const Group = db.model('Group', groupSchema); const [alice, bob] = await User.create({ name: 'Alice' }, { name: 'Bob' });
const group = await Group.create({ name: 'mongoose', _users: [alice, bob] }); const foundGroup = await Group.findById(group).populate('_users').exec(); assert.ok(foundGroup.toJSON()._users[0].hello); }); it('jsonifying with undefined path (gh-11922)', async function() { const userSchema = new Schema({ name: String, friends: [{ type: String, transform(friendName) { return `Hi, ${friendName}`; } }] }); const User = db.model('User', userSchema); const alice = await User.create({ name: 'Alic', friends: ['Bob', 'Jack'] }); const foundAlice = await User.findById(alice._id, { name: true }); assert.equal(foundAlice.friends, undefined); const foundAlicJson = foundAlice.toJSON(); assert.equal(foundAlicJson.friends, undefined); assert.equal(foundAlicJson.name, 'Alic'); }); }); describe('inspect', function() { it('inspect inherits schema options (gh-4001)', async function() { const opts = { toObject: { virtuals: true }, toJSON: { virtuals: true } }; const taskSchema = mongoose.Schema({ name: { type: String, required: true } }, opts); taskSchema.virtual('title'). get(function() { return this.name; }). set(function(title) { this.name = title; }); const Task = db.model('Test', taskSchema); const doc = { name: 'task1', title: 'task999' }; await Task.collection.insertOne(doc); const foundDoc = await Task.findById(doc._id); assert.equal(foundDoc.inspect().title, 'task1'); }); it('does not apply transform to populated docs (gh-4213)', async function() { const UserSchema = new Schema({ name: String }); const PostSchema = new Schema({ title: String, postedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } }, { toObject: { transform: function(doc, ret) { delete ret._id; } }, toJSON: { transform: function(doc, ret) { delete ret._id; } } }); const User = db.model('User', UserSchema); const Post = db.model('BlogPost', PostSchema); const val = new User({ name: 'Val' }); const post = new Post({ title: 'Test', postedBy: val._id }); await Post.create(post); await User.create(val); const posts = await Post.find({}). populate('postedBy'). exec(); assert.equal(posts.length, 1); assert.ok(posts[0].postedBy._id); }); it('handles infinite recursion (gh-11756)', function() { const User = db.model('User', Schema({ name: { type: String, required: true }, posts: [{ type: mongoose.Types.ObjectId, ref: 'Post' }] })); const Post = db.model('Post', Schema({ creator: { type: Schema.Types.ObjectId, ref: 'User' } })); const user = new User({ name: 'Test', posts: [] }); const post = new Post({ creator: user }); user.posts.push(post); const inspected = post.inspect(); assert.ok(inspected); assert.equal(inspected.creator.posts[0].creator.name, 'Test'); }); it('populate on nested path (gh-5703)', function() { const toySchema = new mongoose.Schema({ color: String }); const Toy = db.model('Cat', toySchema); const childSchema = new mongoose.Schema({ name: String, values: { toy: { type: mongoose.Schema.Types.ObjectId, ref: 'Cat' } } }); const Child = db.model('Child', childSchema); return Toy.create({ color: 'brown' }). then(function(toy) { return Child.create({ values: { toy: toy._id } }); }). then(function(child) { return Child.findById(child._id); }). then(function(child) { return child.values.populate('toy').then(function() { return child; }); }). then(function(child) { assert.equal(child.values.toy.color, 'brown'); }); }); }); describe.skip('#update', function() { it('returns a Query', function() { const mg = new mongoose.Mongoose(); const M = mg.model('Test', { s: String }); const doc = new M(); assert.ok(doc.update() instanceof Query); }); it('calling update on document should relay to its model (gh-794)', async function() { const Docs = new Schema({ text: String }); const docs = db.model('Test', Docs); const d = new docs({ text: 'A doc' }); let called = false; await d.save(); const oldUpdate = docs.update; docs.update = function(query, operation) { assert.equal(Object.keys(query).length, 1); assert.equal(d._id, query._id); assert.equal(Object.keys(operation).length, 1); assert.equal(Object.keys(operation.$set).length, 1); assert.equal(operation.$set.text, 'A changed doc'); called = true; docs.update = oldUpdate; oldUpdate.apply(docs, arguments); }; await d.update({ $set: { text: 'A changed doc' } }); assert.equal(called, true); }); }); it('toObject should not set undefined values to null', function() { const doc = new TestDocument(); const obj = doc.toObject(); delete obj._id; assert.deepEqual(obj, { numbers: [], oids: [], em: [] }); }); describe('Errors', function() { it('MongooseErrors should be instances of Error (gh-209)', function() { const MongooseError = require('../lib/error'); const err = new MongooseError('Some message'); assert.ok(err instanceof Error); }); it('ValidationErrors should be instances of Error', function() { const ValidationError = Document.ValidationError; const err = new ValidationError(new TestDocument()); assert.ok(err instanceof Error); }); }); it('methods on embedded docs should work', function() { const ESchema = new Schema({ name: String }); ESchema.methods.test = function() { return this.name + ' butter'; }; ESchema.statics.ten = function() { return 10; }; const E = db.model('Test', ESchema); const PSchema = new Schema({ embed: [ESchema] }); const P = db.model('Test2', PSchema); let p = new P({ embed: [{ name: 'peanut' }] }); assert.equal(typeof p.embed[0].test, 'function'); assert.equal(typeof E.ten, 'function'); assert.equal(p.embed[0].test(), 'peanut butter'); assert.equal(E.ten(), 10); // test push casting p = new P(); p.embed.push({ name: 'apple' }); assert.equal(typeof p.embed[0].test, 'function'); assert.equal(typeof E.ten, 'function'); assert.equal(p.embed[0].test(), 'apple butter'); }); it('setting a positional path does not cast value to array', function() { const doc = new TestDocument(); doc.init({ numbers: [1, 3] }); assert.equal(doc.numbers[0], 1); assert.equal(doc.numbers[1], 3); doc.set('numbers.1', 2); assert.equal(doc.numbers[0], 1); assert.equal(doc.numbers[1], 2); }); it('no maxListeners warning should occur', function() { let traced = false; const trace = console.trace; console.trace = function() { traced = true; console.trace = trace; }; const schema = new Schema({ title: String, embed1: [new Schema({ name: String })], embed2: [new Schema({ name: String })], embed3: [new Schema({ name: String })], embed4: [new Schema({ name: String })], embed5: [new Schema({ name: String })], embed6: [new Schema({ name: String })], embed7: [new Schema({ name: String })], embed8: [new Schema({ name: String })], embed9: [new Schema({ name: String })], embed10: [new Schema({ name: String })], embed11: [new Schema({ name: String })] }); const S = db.model('Test', schema); new S({ title: 'test' }); assert.equal(traced, false); }); it('unselected required fields should pass validation', async function() { const userSchema = new Schema({ name: String, req: { type: String, required: true } }); const User = db.model('Test', userSchema); const user = await User.create({ name: 'teeee', req: 'i am required' }); const user1 = await User.findById(user).select('name').exec(); assert.equal(user1.req, void 0); user1.name = 'wooo'; await user1.save(); const user2 = await User.findById(user1).select('name').exec(); user2.req = undefined; let err = await user2.save().then(() => null, err => err); err = String(err); const invalid = /Path `req` is required./.test(err); assert.ok(invalid); user2.req = 'it works again'; await user2.save(); const user3 = await User.findById(user2).select('_id').exec(); await user3.save(); }); describe('#validate', function() { it('works (gh-891)', async function() { let schema = null; let called = false; const validate = [function() { called = true; return true; }, 'BAM']; schema = new Schema({ prop: { type: String, required: true, validate: validate }, nick: { type: String, required: true } }); const M = db.model('Test', schema); const m = new M({ prop: 'gh891', nick: 'validation test' }); await m.save(); assert.equal(called, true); called = false; const m2 = await M.findById(m, 'nick'); assert.equal(called, false); m2.nick = 'gh-891'; await m2.save(); assert.equal(called, false); }); it('can return a promise', async function() { let schema = null; const validate = [function() { return true; }, 'BAM']; schema = new Schema({ prop: { type: String, required: true, validate: validate }, nick: { type: String, required: true } }); const M = db.model('Test', schema); const m = new M({ prop: 'gh891', nick: 'validation test' }); const mBad = new M({ prop: 'other' }); await m.validate().then(res => res); const err = await mBad.validate().then(() => null, err => err); assert.ok(err); }); it('doesnt have stale cast errors (gh-2766)', async function() { const testSchema = new Schema({ name: String }); const M = db.model('Test', testSchema); const m = new M({ _id: 'this is not a valid _id' }); assert.ok(!m.$isValid('_id')); assert.ok(m.validateSync().errors['_id'].name, 'CastError'); m._id = '000000000000000000000001'; assert.ok(m.$isValid('_id')); assert.ifError(m.validateSync()); await m.validate(); }); it('cast errors persist across validate() calls (gh-2766)', async function() { const db = start(); const testSchema = new Schema({ name: String }); const M = db.model('Test', testSchema); const m = new M({ _id: 'this is not a valid _id' }); assert.ok(!m.$isValid('_id')); const error = await m.validate().then(() => null, err => err); assert.ok(error); assert.equal(error.errors['_id'].name, 'CastError'); const error2 = await m.validate().then(() => null, err => err); assert.ok(error2); assert.equal(error2.errors['_id'].name, 'CastError'); const err1 = m.validateSync(); const err2 = m.validateSync(); assert.equal(err1.errors['_id'].name, 'CastError'); assert.equal(err2.errors['_id'].name, 'CastError'); await db.close(); }); it('returns a promise when there are no validators', function(done) { let schema = null; schema = new Schema({ _id: String }); const M = db.model('Test', schema); const m = new M(); const promise = m.validate(); promise.then(function() { clearTimeout(timeout); done(); }); const timeout = setTimeout(function() { db.close(); throw new Error('Promise not fulfilled!'); }, 500); }); describe('works on arrays', function() { it('with required', function(done) { const schema = new Schema({ name: String, arr: { type: [], required: true } }); const M = db.model('Test', schema); const m = new M({ name: 'gh1109-1', arr: null }); m.save(function(err) { assert.ok(/Path `arr` is required/.test(err)); m.arr = null; m.save(function(err) { assert.ok(/Path `arr` is required/.test(err)); m.arr = []; m.arr.push('works'); m.save(function(err) { assert.ifError(err); done(); }); }); }); }); it('with custom validator', function(done) { let called = false; function validator(val) { called = true; return val && val.length > 1; } const validate = [validator, 'BAM']; const schema = new Schema({ arr: { type: [], validate: validate } }); const M = db.model('Test', schema); const m = new M({ name: 'gh1109-2', arr: [1] }); assert.equal(called, false); m.save(function(err) { assert.equal(String(err), 'ValidationError: arr: BAM'); assert.equal(called, true); m.arr.push(2); called = false; m.save(function(err) { assert.equal(called, true); assert.ifError(err); done(); }); }); }); it('with both required + custom validator', function(done) { function validator(val) { return val && val.length > 1; } const validate = [validator, 'BAM']; const schema = new Schema({ arr: { type: [], required: true, validate: validate } }); const M = db.model('Test', schema); const m = new M({ name: 'gh1109-3', arr: null }); m.save(function(err) { assert.equal(err.errors.arr.message, 'Path `arr` is required.'); m.arr = [{ nice: true }]; m.save(function(err) { assert.equal(String(err), 'ValidationError: arr: BAM'); m.arr.push(95); m.save(function(err) { assert.ifError(err); done(); }); }); }); }); }); it('validator should run only once gh-1743', function(done) { let count = 0; const Control = new Schema({ test: { type: String, validate: function(value, done) { count++; return done(true); } } }); const PostSchema = new Schema({ controls: [Control] }); const Post = db.model('BlogPost', PostSchema); const post = new Post({ controls: [{ test: 'xx' }] }); post.save(function() { assert.equal(count, 1); done(); }); }); it('validator should run only once per sub-doc gh-1743', async function() { this.timeout(4500); let count = 0; const db = start(); const Control = new Schema({ test: { type: String, validate: function() { count++; } } }); const PostSchema = new Schema({ controls: [Control] }); const Post = db.model('BlogPost', PostSchema); const post = new Post({ controls: [ { test: 'xx' }, { test: 'yy' } ] }); await post.save(); assert.equal(count, post.controls.length); await db.close(); }); }); it('#invalidate', function(done) { let InvalidateSchema = null; let Post = null; let post = null; InvalidateSchema = new Schema({ prop: { type: String } }, { strict: false }); Post = db.model('Test', InvalidateSchema); post = new Post(); post.set({ baz: 'val' }); const _err = post.invalidate('baz', 'validation failed for path {PATH}', 'val', 'custom error'); assert.ok(_err instanceof ValidationError); post.save(function(err) { assert.ok(err instanceof MongooseError); assert.ok(err instanceof ValidationError); assert.ok(err.errors.baz instanceof ValidatorError); assert.equal(err.errors.baz.message, 'validation failed for path baz'); assert.equal(err.errors.baz.path, 'baz'); assert.equal(err.errors.baz.value, 'val'); assert.equal(err.errors.baz.kind, 'custom error'); post.save(function(err) { assert.strictEqual(err, null); done(); }); }); }); describe('#equals', function() { describe('should work', function() { let S; let N; let O; let B; let M; before(function() { db.deleteModel(/^Test/); S = db.model('Test', new Schema({ _id: String })); N = db.model('Test2', new Schema({ _id: Number })); O = db.model('Test3', new Schema({ _id: Schema.ObjectId })); B = db.model('Test4', new Schema({ _id: Buffer })); M = db.model('Test5', new Schema({ name: String }, { _id: false })); }); it('with string _ids', function() { const s1 = new S({ _id: 'one' }); const s2 = new S({ _id: 'one' }); assert.ok(s1.equals(s2)); }); it('with number _ids', function() { const n1 = new N({ _id: 0 }); const n2 = new N({ _id: 0 }); assert.ok(n1.equals(n2)); }); it('with ObjectId _ids', function() { let id = new mongoose.Types.ObjectId(); let o1 = new O({ _id: id }); let o2 = new O({ _id: id }); assert.ok(o1.equals(o2)); id = String(new mongoose.Types.ObjectId()); o1 = new O({ _id: id }); o2 = new O({ _id: id }); assert.ok(o1.equals(o2)); }); it('with Buffer _ids', function() { const n1 = new B({ _id: 0 }); const n2 = new B({ _id: 0 }); assert.ok(n1.equals(n2)); }); it('with _id disabled (gh-1687)', function() { const m1 = new M(); const m2 = new M(); assert.doesNotThrow(function() { m1.equals(m2); }); }); }); }); describe('setter', function() { describe('order', function() { it('is applied correctly', function() { const date = 'Thu Aug 16 2012 09:45:59 GMT-0700'; const d = new TestDocument(); dateSetterCalled = false; d.date = date; assert.ok(dateSetterCalled); dateSetterCalled = false; assert.ok(d._doc.date instanceof Date); assert.ok(d.date instanceof Date); assert.equal(+d.date, +new Date(date)); }); }); it('works with undefined (gh-1892)', function(done) { const d = new TestDocument(); d.nested.setr = undefined; assert.equal(d.nested.setr, 'undefined setter'); dateSetterCalled = false; d.date = undefined; d.validate(function(err) { assert.ifError(err); assert.ok(dateSetterCalled); done(); }); }); it('passes priorVal (gh-8629)', function() { const names = []; const profiles = []; const Model = db.model('Test', Schema({ name: { type: String, set: (v, priorVal) => { names.push(priorVal); return v; } }, profile: { type: Schema({ age: Number }, { _id: false }), set: (v, priorVal) => { profiles.push(priorVal == null ? priorVal : priorVal.toObject()); return v; } } })); const doc = new Model({ name: 'test', profile: { age: 29 } }); assert.deepEqual(names, [null]); assert.deepEqual(profiles, [null]); doc.name = 'test2'; doc.profile = { age: 30 }; assert.deepEqual(names, [null, 'test']); assert.deepEqual(profiles, [null, { age: 29 }]); }); describe('on nested paths', function() { describe('using set(path, object)', function() { it('overwrites the entire object', function() { const doc = new TestDocument(); doc.init({ test: 'Test', nested: { age: 5 } }); doc.set('nested', { path: 'overwrite the entire nested object' }); assert.equal(doc.nested.age, undefined); assert.equal(Object.keys(doc._doc.nested).length, 1); assert.equal(doc.nested.path, 'overwrite the entire nested object'); assert.ok(doc.isModified('nested')); }); it('allows positional syntax on mixed nested paths (gh-6738)', function() { const schema = new Schema({ nested: {} }); const M = db.model('Test', schema); const doc = new M({ 'nested.x': 'foo', 'nested.y': 42, 'nested.a.b.c': { d: { e: { f: 'g' } } } }); assert.strictEqual(doc.nested.x, 'foo'); assert.strictEqual(doc.nested.y, 42); assert.strictEqual(doc.nested.a.b.c.d.e.f, 'g'); }); it('gh-1954', function() { const schema = new Schema({ schedule: [new Schema({ open: Number, close: Number })] }); const M = db.model('BlogPost', schema); const doc = new M({ schedule: [{ open: 1000, close: 1900 }] }); assert.ok(doc.schedule[0] instanceof ArraySubdocument); doc.set('schedule.0.open', 1100); assert.ok(doc.schedule); assert.ok(doc.schedule.isMongooseDocumentArray); assert.ok(doc.schedule[0] instanceof ArraySubdocument); assert.equal(doc.schedule[0].open, 1100); assert.equal(doc.schedule[0].close, 1900); }); }); describe('when overwriting with a document instance', function() { it('does not cause StackOverflows (gh-1234)', function() { const doc = new TestDocument({ nested: { age: 35 } }); doc.nested = doc.nested; assert.doesNotThrow(function() { doc.nested.age; }); }); }); }); }); describe('virtual', function() { describe('setter', function() { let val; let M; beforeEach(function() { const schema = new mongoose.Schema({ v: Number }); schema.virtual('thang').set(function(v) { val = v; }); db.deleteModel(/Test/); M = db.model('Test', schema); }); it('works with objects', function() { new M({ thang: {} }); assert.deepEqual({}, val); }); it('works with arrays', function() { new M({ thang: [] }); assert.deepEqual([], val); }); it('works with numbers', function() { new M({ thang: 4 }); assert.deepEqual(4, val); }); it('works with strings', function() { new M({ thang: '3' }); assert.deepEqual('3', val); }); }); it('passes doc as third param for arrow functions (gh-4143)', function() { const schema = new mongoose.Schema({ name: { first: String, last: String } }); schema.virtual('fullname'). get((v, virtual, doc) => `${doc.name.first} ${doc.name.last}`). set((v, virtual, doc) => { const parts = v.split(' '); doc.name.last = parts[parts.length - 1]; doc.name.first = parts.slice(0, parts.length - 1).join(' '); }); const Model = db.model('Person', schema); const doc = new Model({ name: { first: 'Jean-Luc', last: 'Picard' } }); assert.equal(doc.fullname, 'Jean-Luc Picard'); doc.fullname = 'Will Riker'; assert.equal(doc.name.first, 'Will'); assert.equal(doc.name.last, 'Riker'); }); }); describe('gh-2082', function() { it('works', async function() { const Parent = db.model('Test', parentSchema); const parent = new Parent({ name: 'Hello' }); await parent.save(); parent.children.push({ counter: 0 }); await parent.save(); parent.children[0].counter += 1; await parent.save(); parent.children[0].counter += 1; await parent.save(); await Parent.findOne({}); assert.equal(parent.children[0].counter, 2); }); }); describe('gh-1933', function() { it('works', async function() { const M = db.model('Test', new Schema({ id: String, field: Number })); const doc = await M.create({}); doc.__v = 123; doc.field = 5; // Does not throw await doc.save(); }); }); describe('gh-1638', function() { it('works', async function() { const ItemChildSchema = new mongoose.Schema({ name: { type: String, required: true, default: 'hello' } }); const ItemParentSchema = new mongoose.Schema({ children: [ItemChildSchema] }); const ItemParent = db.model('Parent', ItemParentSchema); const ItemChild = db.model('Child', ItemChildSchema); const c1 = new ItemChild({ name: 'first child' }); const c2 = new ItemChild({ name: 'second child' }); const p = new ItemParent({ children: [c1, c2] }); await p.save(); c2.name = 'updated 2'; p.children = [c2]; await p.save(); assert.equal(p.children.length, 1); }); }); describe('gh-2434', function() { it('will save the new value', async function() { const ItemSchema = new mongoose.Schema({ st: Number, s: [] }); const Item = db.model('Test', ItemSchema); const item = new Item({ st: 1 }); await item.save(); item.st = 3; item.s = []; await item.save(); // item.st is 3 but may not be saved to DB const doc = await Item.findById(item._id); assert.equal(doc.st, 3); }); }); describe('gh-8371', function() { beforeEach(async() => { const Person = db.model('Person', Schema({ name: String })); await Person.deleteMany({}); db.deleteModel('Person'); }); it('setting isNew to true makes save tries to insert a new document (gh-8371)', async function() { const personSchema = new Schema({ name: String }); const Person = db.model('Person', personSchema); const createdPerson = await Person.create({ name: 'Hafez' }); const removedPerson = await Person.findOneAndRemove({ _id: createdPerson._id }); removedPerson.isNew = true; await removedPerson.save(); const foundPerson = await Person.findOne({ _id: removedPerson._id }); assert.ok(foundPerson); }); it('setting isNew to true throws an error when a document already exists (gh-8371)', async function() { const personSchema = new Schema({ name: String }); const Person = db.model('Person', personSchema); const createdPerson = await Person.create({ name: 'Hafez' }); createdPerson.isNew = true; let threw = false; try { await createdPerson.save(); } catch (err) { threw = true; assert.equal(err.code, 11000); } assert.equal(threw, true); }); it('saving a document with no changes, throws an error when document is not found', async function() { const personSchema = new Schema({ name: String }); const Person = db.model('Person', personSchema); const person = await Person.create({ name: 'Hafez' }); await Person.deleteOne({ _id: person._id }); const err = await person.save().then(() => null, err => err); assert.equal(err instanceof DocumentNotFoundError, true); assert.equal(err.message, `No document found for query "{ _id: new ObjectId("${person._id}") }" on model "Person"`); }); it('saving a document when version bump required, throws a VersionError when document is not found (gh-10974)', async function() { const personSchema = new Schema({ tags: [String] }); const Person = db.model('Person', personSchema); const person = await Person.create({ tags: ['tag1', 'tag2'] }); await Person.deleteOne({ _id: person._id }); person.tags.splice(0, 1); const err = await person.save().then(() => null, err => err); assert.ok(err instanceof VersionError); assert.equal(err.message, `No matching document found for id "${person._id}" version 0 modifiedPaths "tags"`); }); it('saving a document with changes, throws an error when document is not found', async function() { const personSchema = new Schema({ name: String }); const Person = db.model('Person', personSchema); const person = await Person.create({ name: 'Hafez' }); await Person.deleteOne({ _id: person._id }); person.name = 'Different Name'; let threw = false; try { await person.save(); } catch (err) { assert.equal(err instanceof DocumentNotFoundError, true); assert.equal(err.message, `No document found for query "{ _id: new ObjectId("${person._id}") }" on model "Person"`); threw = true; } assert.equal(threw, true); }); it('passes save custom options to Model.exists(...) when no changes are present (gh-8739)', async function() { const personSchema = new Schema({ name: String }); let optionInMiddleware; personSchema.pre('findOne', function(next) { optionInMiddleware = this.getOptions().customOption; return next(); }); const Person = db.model('Person', personSchema); const person = await Person.create({ name: 'Hafez' }); await person.save({ customOption: 'test' }); assert.equal(optionInMiddleware, 'test'); }); }); it('properly calls queue functions (gh-2856)', function() { const personSchema = new mongoose.Schema({ name: String }); let calledName; personSchema.methods.fn = function() { calledName = this.name; }; personSchema.queue('fn'); const Person = db.model('Person', personSchema); new Person({ name: 'Val' }); assert.equal(calledName, 'Val'); }); describe('bug fixes', function() { it('applies toJSON transform correctly for populated docs (gh-2910) (gh-2990)', async function() { const parentSchema = mongoose.Schema({ c: { type: mongoose.Schema.Types.ObjectId, ref: 'Child' } }); let called = []; parentSchema.options.toJSON = { transform: function(doc, ret) { called.push(ret); return ret; } }; const childSchema = mongoose.Schema({ name: String }); let childCalled = []; childSchema.options.toJSON = { transform: function(doc, ret) { childCalled.push(ret); return ret; } }; const Child = db.model('Child', childSchema); const Parent = db.model('Parent', parentSchema); const c = await Child.create({ name: 'test' }); const createdParent = await Parent.create({ c: c._id }); const p = await Parent.findOne({ _id: createdParent._id }).populate('c').exec(); let doc = p.toJSON(); assert.equal(called.length, 1); assert.equal(called[0]._id.toString(), p._id.toString()); assert.equal(doc._id.toString(), p._id.toString()); assert.equal(childCalled.length, 1); assert.equal(childCalled[0]._id.toString(), c._id.toString()); called = []; childCalled = []; // JSON.stringify() passes field name, so make sure we don't treat // that as a param to toJSON (gh-2990) doc = JSON.parse(JSON.stringify({ parent: p })).parent; assert.equal(called.length, 1); assert.equal(called[0]._id.toString(), p._id.toString()); assert.equal(doc._id.toString(), p._id.toString()); assert.equal(childCalled.length, 1); assert.equal(childCalled[0]._id.toString(), c._id.toString()); }); it('single nested schema transform with save() (gh-5807)', function() { const embeddedSchema = new Schema({ test: String }); let called = false; embeddedSchema.options.toObject = { transform: function(doc, ret) { called = true; delete ret.test; return ret; } }; const topLevelSchema = new Schema({ embedded: embeddedSchema }); const MyModel = db.model('Test', topLevelSchema); return MyModel.create({}). then(function(doc) { doc.embedded = { test: '123' }; return doc.save(); }). then(function(doc) { return MyModel.findById(doc._id); }). then(function(doc) { assert.equal(doc.embedded.test, '123'); assert.ok(!called); }); }); it('setters firing with objects on real paths (gh-2943)', function() { const M = db.model('Test', { myStr: { type: String, set: function(v) { return v.value; } }, otherStr: String }); const t = new M({ myStr: { value: 'test' } }); assert.equal(t.myStr, 'test'); new M({ otherStr: { value: 'test' } }); assert.ok(!t.otherStr); }); describe('gh-2782', function() { it('should set data from a sub doc', function() { const schema1 = new mongoose.Schema({ data: { email: String } }); const schema2 = new mongoose.Schema({ email: String }); const Model1 = db.model('Test', schema1); const Model2 = db.model('Test1', schema2); const doc1 = new Model1({ 'data.email': 'some@example.com' }); assert.equal(doc1.data.email, 'some@example.com'); const doc2 = new Model2(); doc2.set(doc1.data); assert.equal(doc2.email, 'some@example.com'); }); }); it('set data from subdoc keys (gh-3346)', function() { const schema1 = new mongoose.Schema({ data: { email: String } }); const Model1 = db.model('Test', schema1); const doc1 = new Model1({ 'data.email': 'some@example.com' }); assert.equal(doc1.data.email, 'some@example.com'); const doc2 = new Model1({ data: doc1.data }); assert.equal(doc2.data.email, 'some@example.com'); }); it('doesnt attempt to cast generic objects as strings (gh-3030)', function(done) { const M = db.model('Test', { myStr: { type: String } }); const t = new M({ myStr: { thisIs: 'anObject' } }); assert.ok(!t.myStr); t.validate(function(error) { assert.ok(error); done(); }); }); it('single embedded schemas 1 (gh-2689)', function(done) { const userSchema = new mongoose.Schema({ name: String, email: String }, { _id: false, id: false }); let userHookCount = 0; userSchema.pre('save', function(next) { ++userHookCount; next(); }); const eventSchema = new mongoose.Schema({ user: userSchema, name: String }); let eventHookCount = 0; eventSchema.pre('save', function(next) { ++eventHookCount; next(); }); const Event = db.model('Event', eventSchema); const e = new Event({ name: 'test', user: { name: 123, email: 'val' } }); e.save(function(error) { assert.ifError(error); assert.strictEqual(e.user.name, '123'); assert.equal(eventHookCount, 1); assert.equal(userHookCount, 1); Event.findOne({ user: { name: '123', email: 'val' } }, function(err, doc) { assert.ifError(err); assert.ok(doc); Event.findOne({ user: { $in: [{ name: '123', email: 'val' }] } }, function(err, doc) { assert.ifError(err); assert.ok(doc); done(); }); }); }); }); it('single embedded schemas with validation (gh-2689)', function() { const userSchema = new mongoose.Schema({ name: String, email: { type: String, required: true, match: /.+@.+/ } }, { _id: false, id: false }); const eventSchema = new mongoose.Schema({ user: userSchema, name: String }); const Event = db.model('Event', eventSchema); const e = new Event({ name: 'test', user: {} }); let error = e.validateSync(); assert.ok(error); assert.ok(error.errors['user.email']); assert.equal(error.errors['user.email'].kind, 'required'); e.user.email = 'val'; error = e.validateSync(); assert.ok(error); assert.ok(error.errors['user.email']); assert.equal(error.errors['user.email'].kind, 'regexp'); }); it('single embedded parent() (gh-5134)', function() { const userSchema = new mongoose.Schema({ name: String, email: { type: String, required: true, match: /.+@.+/ } }, { _id: false, id: false }); const eventSchema = new mongoose.Schema({ user: userSchema, name: String }); const Event = db.model('Event', eventSchema); const e = new Event({ name: 'test', user: {} }); assert.strictEqual(e.user.parent(), e.user.ownerDocument()); }); it('single embedded schemas with markmodified (gh-2689)', async function() { const userSchema = new mongoose.Schema({ name: String, email: { type: String, required: true, match: /.+@.+/ } }, { _id: false, id: false }); const eventSchema = new mongoose.Schema({ user: userSchema, name: String }); const Event = db.model('Event', eventSchema); const e = new Event({ name: 'test', user: { email: 'a@b' } }); const doc = await e.save(); assert.ok(doc); assert.ok(!doc.isModified('user')); assert.ok(!doc.isModified('user.email')); assert.ok(!doc.isModified('user.name')); doc.user.name = 'Val'; assert.ok(doc.isModified('user')); assert.ok(!doc.isModified('user.email')); assert.ok(doc.isModified('user.name')); const delta = doc.$__delta()[1]; assert.deepEqual(delta, { $set: { 'user.name': 'Val' } }); await doc.save(); const event = await Event.findOne({ _id: doc._id }); assert.deepEqual(event.user.toObject(), { email: 'a@b', name: 'Val' }); }); it('single embedded schemas + update validators (gh-2689)', async function() { const userSchema = new mongoose.Schema({ name: { type: String, default: 'Val' }, email: { type: String, required: true, match: /.+@.+/ } }, { _id: false, id: false }); const eventSchema = new mongoose.Schema({ user: userSchema, name: String }); const Event = db.model('Event', eventSchema); const badUpdate = { $set: { 'user.email': 'a' } }; const options = { runValidators: true }; const error = await Event.updateOne({}, badUpdate, options).then(() => null, err => err); assert.ok(error); assert.equal(error.errors['user.email'].kind, 'regexp'); const nestedUpdate = { name: 'test', user: {} }; // Does not throw await Event.updateOne({}, nestedUpdate, { upsert: true }); const ev = await Event.findOne({ name: 'test' }); assert.equal(ev.user.name, 'Val'); }); it('single embedded schema update validators ignore _id (gh-6269)', async function() { const subDocSchema = new mongoose.Schema({ name: String }); const schema = new mongoose.Schema({ subDoc: subDocSchema, test: String }); const Model = db.model('Test', schema); const fakeDoc = new Model({}); await Model.create({}); const res = await Model.findOneAndUpdate( { _id: fakeDoc._id }, { test: 'test' }, { upsert: true, new: true } ); assert.equal(res.test, 'test'); assert.ok(!res.subDoc); }); }); describe('error processing (gh-2284)', async function() { it('save errors', async function() { const schema = new Schema({ name: { type: String, required: true } }); schema.post('save', function(error, doc, next) { assert.ok(doc instanceof Model); next(new Error('Catch all')); }); schema.post('save', function(error, doc, next) { assert.ok(doc instanceof Model); next(new Error('Catch all #2')); }); const Model = db.model('Test', schema); const error = await Model.create({}).then(() => null, err => err); assert.ok(error); assert.equal(error.message, 'Catch all #2'); }); it('validate errors (gh-4885)', async function() { const testSchema = new Schema({ title: { type: String, required: true } }); let called = 0; testSchema.post('validate', function(error, doc, next) { ++called; next(error); }); const Test = db.model('Test', testSchema); const error = await Test.create({}).then(() => null, err => err); assert.ok(error); assert.equal(called, 1); }); it('does not filter validation on unmodified paths when validateModifiedOnly not set (gh-7421)', async function() { const testSchema = new Schema({ title: { type: String, required: true }, other: String }); const Test = db.model('Test', testSchema); const docs = await Test.create([{}], { validateBeforeSave: false }); const doc = docs[0]; doc.other = 'something'; assert.ok(doc.validateSync().errors); const error = await doc.save().then(() => null, err => err); assert.ok(error.errors); }); it('filters out validation on unmodified paths when validateModifiedOnly set (gh-7421) (gh-9963)', async function() { const testSchema = new Schema({ title: { type: String, required: true }, other: String, subdocs: [{ name: { type: String, required: true } }] }); const Test = db.model('Test', testSchema); const docs = await Test.create( [{ subdocs: [{ name: null }, { name: 'test' }] }], { validateBeforeSave: false } ); const doc = docs[0]; doc.other = 'something'; doc.subdocs[1].name = 'test2'; assert.equal(doc.validateSync({ validateModifiedOnly: true }), null); assert.equal(doc.validateSync('other'), null); assert.ok(doc.validateSync('other title').errors['title']); // Does not throw await doc.save({ validateModifiedOnly: true }); }); it('does not filter validation on modified paths when validateModifiedOnly set (gh-7421)', async function() { const testSchema = new Schema({ title: { type: String, required: true }, other: String }); const Test = db.model('Test', testSchema); const docs = await Test.create([{ title: 'title' }], { validateBeforeSave: false }); const doc = docs[0]; doc.title = ''; assert.ok(doc.validateSync({ validateModifiedOnly: true }).errors); const error = await doc.save({ validateModifiedOnly: true }).then(() => null, err => err); assert.ok(error.errors); }); it('validateModifiedOnly with pre existing validation error (gh-8091)', async function() { const schema = mongoose.Schema({ title: String, coverId: Number }, { validateModifiedOnly: true }); const Model = db.model('Test', schema);
await Model.collection.insertOne({ title: 'foo', coverId: parseFloat('not a number') }); const doc = await Model.findOne(); doc.title = 'bar'; // Should not throw await doc.save(); }); it('handles non-errors', async function() { const schema = new Schema({ name: { type: String, required: true } }); schema.post('save', function(error, doc, next) { next(new Error('Catch all')); }); schema.post('save', function(error, doc, next) { next(new Error('Catch all #2')); }); const Model = db.model('Test', schema); // Does not throw await Model.create({ name: 'test' }); }); }); describe('bug fixes', function() { beforeEach(() => db.deleteModel(/.*/)); it('single embedded schemas with populate (gh-3501)', async function() { const PopulateMeSchema = new Schema({}); const Child = db.model('Child', PopulateMeSchema); const SingleNestedSchema = new Schema({ populateMeArray: [{ type: Schema.Types.ObjectId, ref: 'Child' }] }); const parentSchema = new Schema({ singleNested: SingleNestedSchema }); const P = db.model('Parent', parentSchema); const docs = await Child.create([{}, {}]); const obj = { singleNested: { populateMeArray: [docs[0]._id, docs[1]._id] } }; const doc = await P.create(obj); const foundDoc = await P. findById(doc._id). populate('singleNested.populateMeArray'). exec(); assert.ok(foundDoc.singleNested.populateMeArray[0]._id); }); it('single embedded schemas with methods (gh-3534)', function() { const personSchema = new Schema({ name: String }); personSchema.methods.firstName = function() { return this.name.substring(0, this.name.indexOf(' ')); }; const bandSchema = new Schema({ leadSinger: personSchema }); const Band = db.model('Band', bandSchema); const gnr = new Band({ leadSinger: { name: 'Axl Rose' } }); assert.equal(gnr.leadSinger.firstName(), 'Axl'); }); it('single embedded schemas with models (gh-3535)', function(done) { const personSchema = new Schema({ name: String }); const Person = db.model('Person', personSchema); const bandSchema = new Schema({ leadSinger: personSchema }); const Band = db.model('Band', bandSchema); const axl = new Person({ name: 'Axl Rose' }); const gnr = new Band({ leadSinger: axl }); gnr.save(function(error) { assert.ifError(error); assert.equal(gnr.leadSinger.name, 'Axl Rose'); done(); }); }); it('single embedded schemas with indexes (gh-3594)', function() { const personSchema = new Schema({ name: { type: String, unique: true } }); const bandSchema = new Schema({ leadSinger: personSchema }); assert.equal(bandSchema.indexes().length, 1); const index = bandSchema.indexes()[0]; assert.deepEqual(index[0], { 'leadSinger.name': 1 }); assert.ok(index[1].unique); }); it('removing single embedded docs (gh-3596)', async function() { const personSchema = new Schema({ name: String }); const bandSchema = new Schema({ guitarist: personSchema, name: String }); const Band = db.model('Band', bandSchema); const gnr = new Band({ name: 'Guns N\' Roses', guitarist: { name: 'Slash' } }); await gnr.save(); gnr.guitarist = undefined; await gnr.save(); assert.ok(!gnr.guitarist); }); it('setting single embedded docs (gh-3601)', async function() { const personSchema = new Schema({ name: String }); const bandSchema = new Schema({ guitarist: personSchema, name: String }); const Band = db.model('Band', bandSchema); const gnr = new Band({ name: 'Guns N\' Roses', guitarist: { name: 'Slash' } }); const velvetRevolver = new Band({ name: 'Velvet Revolver' }); velvetRevolver.guitarist = gnr.guitarist; await velvetRevolver.save(); assert.equal(velvetRevolver.guitarist.name, 'Slash'); }); it('single embedded docs init obeys strict mode (gh-3642)', async function() { const personSchema = new Schema({ name: String }); const bandSchema = new Schema({ guitarist: personSchema, name: String }); const Band = db.model('Band', bandSchema); const velvetRevolver = new Band({ name: 'Velvet Revolver', guitarist: { name: 'Slash', realName: 'Saul Hudson' } }); await velvetRevolver.save(); const query = { name: 'Velvet Revolver' }; const band = await Band.collection.findOne(query); assert.ok(!band.guitarist.realName); }); it('single embedded docs post hooks (gh-3679)', async function() { const postHookCalls = []; const personSchema = new Schema({ name: String }); personSchema.post('save', function() { postHookCalls.push(this); }); const bandSchema = new Schema({ guitarist: personSchema, name: String }); const Band = db.model('Band', bandSchema); const obj = { name: 'Guns N\' Roses', guitarist: { name: 'Slash' } }; await Band.create(obj); await new Promise((resolve) => { setTimeout(function() { assert.equal(postHookCalls.length, 1); assert.equal(postHookCalls[0].name, 'Slash'); resolve(); }); }); }); it('single embedded docs .set() (gh-3686)', async function() { const personSchema = new Schema({ name: String, realName: String }); const bandSchema = new Schema({ guitarist: personSchema, name: String }); const Band = db.model('Band', bandSchema); const obj = { name: 'Guns N\' Roses', guitarist: { name: 'Slash', realName: 'Saul Hudson' } }; const gnr = await Band.create(obj); gnr.set('guitarist.name', 'Buckethead'); await gnr.save(); assert.equal(gnr.guitarist.name, 'Buckethead'); assert.equal(gnr.guitarist.realName, 'Saul Hudson'); }); it('single embedded docs with arrays pre hooks (gh-3680)', async function() { const childSchema = new Schema({ count: Number }); let preCalls = 0; childSchema.pre('save', function(next) { ++preCalls; next(); }); const SingleNestedSchema = new Schema({ children: [childSchema] }); const ParentSchema = new Schema({ singleNested: SingleNestedSchema }); const Parent = db.model('Parent', ParentSchema); const obj = { singleNested: { children: [{ count: 0 }] } }; await Parent.create(obj); assert.equal(preCalls, 1); }); it('nested single embedded doc validation (gh-3702)', function(done) { const childChildSchema = new Schema({ count: { type: Number, min: 1 } }); const childSchema = new Schema({ child: childChildSchema }); const parentSchema = new Schema({ child: childSchema }); const Parent = db.model('Parent', parentSchema); const obj = { child: { child: { count: 0 } } }; Parent.create(obj, function(error) { assert.ok(error); assert.ok(/ValidationError/.test(error.toString())); done(); }); }); it('handles virtuals with dots correctly (gh-3618)', function() { const testSchema = new Schema({ nested: { type: Object, default: {} } }); testSchema.virtual('nested.test').get(function() { return true; }); const Test = db.model('Test', testSchema); const test = new Test(); let doc = test.toObject({ getters: true, virtuals: true }); delete doc._id; delete doc.id; assert.deepEqual(doc, { nested: { test: true } }); doc = test.toObject({ getters: false, virtuals: true }); delete doc._id; delete doc.id; assert.deepEqual(doc, { nested: { test: true } }); }); it('handles pushing with numeric keys (gh-3623)', async function() { const schema = new Schema({ array: [{ 1: { date: Date }, 2: { date: Date }, 3: { date: Date } }] }); const MyModel = db.model('Test', schema); const doc = { array: [{ 2: {} }] }; await MyModel.collection.insertOne(doc); const foundDoc = await MyModel.findOne({ _id: doc._id }); foundDoc.array.push({ 2: {} }); await foundDoc.save(); }); it('handles 0 for numeric subdoc ids (gh-3776)', async function() { const personSchema = new Schema({ _id: Number, name: String, age: Number, friends: [{ type: Number, ref: 'Person' }] }); const Person = db.model('Person', personSchema);
const people = await Person.create([ { _id: 0, name: 'Alice' }, { _id: 1, name: 'Bob' } ]); const alice = people[0]; alice.friends.push(people[1]); // Should not throw await alice.save(); }); it('handles conflicting names (gh-3867)', function() { const testSchema = new Schema({ name: { type: String, required: true }, things: [{ name: { type: String, required: true } }] }); const M = db.model('Test', testSchema); const doc = M({ things: [{}] }); const fields = Object.keys(doc.validateSync().errors).sort(); assert.deepEqual(fields, ['name', 'things.0.name']); }); it('populate with lean (gh-3873)', async function() { const companySchema = new mongoose.Schema({ name: String, description: String, userCnt: { type: Number, default: 0, select: false } }); const userSchema = new mongoose.Schema({ name: String, company: { type: mongoose.Schema.Types.ObjectId, ref: 'Company' } }); const Company = db.model('Company', companySchema); const User = db.model('User', userSchema); const company = new Company({ name: 'IniTech', userCnt: 1 }); const user = new User({ name: 'Peter', company: company._id }); await company.save(); await user.save(); const pop = { path: 'company', select: 'name', options: { lean: true } }; const docs = await User.find({}).populate(pop).exec(); assert.equal(docs.length, 1); assert.strictEqual(docs[0].company.userCnt, undefined); }); it('init single nested subdoc with select (gh-3880)', async function() { const childSchema = new mongoose.Schema({ name: { type: String }, friends: [{ type: String }] }); const parentSchema = new mongoose.Schema({ name: { type: String }, child: childSchema }); const Parent = db.model('Parent', parentSchema); const p = new Parent({ name: 'Mufasa', child: { name: 'Simba', friends: ['Pumbaa', 'Timon', 'Nala'] } }); await p.save(); const fields = 'name child.name'; const doc = await Parent.findById(p._id).select(fields).exec(); assert.strictEqual(doc.child.friends, void 0); }); it('single nested subdoc isModified() (gh-3910)', async function() { let called = 0; const ChildSchema = new Schema({ name: String }); ChildSchema.pre('save', function(next) { assert.ok(this.isModified('name')); ++called; next(); }); const ParentSchema = new Schema({ name: String, child: ChildSchema }); const Parent = db.model('Parent', ParentSchema); const p = new Parent({ name: 'Darth Vader', child: { name: 'Luke Skywalker' } }); await p.save(); assert.strictEqual(called, 1); }); it('pre and post as schema keys (gh-3902)', async function() { const schema = new mongoose.Schema({ pre: String, post: String }, { versionKey: false }); const MyModel = db.model('Test', schema); const doc = await MyModel.create({ pre: 'test', post: 'test' }); assert.deepEqual( utils.omit(doc.toObject(), '_id'), { pre: 'test', post: 'test' } ); }); it('manual population and isNew (gh-3982)', async function() { const NestedModelSchema = new mongoose.Schema({ field: String }); const NestedModel = db.model('Test', NestedModelSchema); const ModelSchema = new mongoose.Schema({ field: String, array: [{ type: mongoose.Schema.ObjectId, ref: 'Test', required: true }] }); const Model = db.model('Test1', ModelSchema); const nestedModel = new NestedModel({ field: 'nestedModel' }); await nestedModel.save(); const doc = await Model.create({ array: [nestedModel._id] }); const foundDoc = await Model.findById(doc._id).populate('array').exec(); foundDoc.array.push(nestedModel); assert.strictEqual(foundDoc.isNew, false); assert.strictEqual(foundDoc.array[0].isNew, false); assert.strictEqual(foundDoc.array[1].isNew, false); assert.strictEqual(nestedModel.isNew, false); }); it('manual population with refPath (gh-7070)', async function() { const ChildModelSchema = new mongoose.Schema({ name: String }); const ChildModel = db.model('Child', ChildModelSchema); const ParentModelSchema = new mongoose.Schema({ model: String, childId: { type: mongoose.ObjectId, refPath: 'model' }, otherId: mongoose.ObjectId }); const ParentModel = db.model('Parent', ParentModelSchema);
const child = await ChildModel.create({ name: 'test' }); let parent = await ParentModel.create({ model: 'Child', childId: child._id }); parent = await ParentModel.findOne(); parent.childId = child; parent.otherId = child; assert.equal(parent.childId.name, 'test'); assert.ok(parent.otherId instanceof mongoose.Types.ObjectId); }); it('doesnt skipId for single nested subdocs (gh-4008)', async function() { const childSchema = new Schema({ name: String }); const parentSchema = new Schema({ child: childSchema }); const Parent = db.model('Parent', parentSchema); const doc = await Parent.create({ child: { name: 'My child' } }); const foundDoc = await Parent.collection.findOne({ _id: doc._id }); assert.ok(foundDoc.child._id); }); it('single embedded docs with $near (gh-4014)', async function() { const schema = new mongoose.Schema({ placeName: String }); const geoSchema = new mongoose.Schema({ type: { type: String, enum: 'Point', default: 'Point' }, coordinates: { type: [Number], default: [0, 0] } }); schema.add({ geo: geoSchema }); schema.index({ geo: '2dsphere' }); const MyModel = db.model('Test', schema); await MyModel.init(); await MyModel. where('geo').near({ center: [50, 50], spherical: true }). exec(); }); it('skip validation if required returns false (gh-4094)', function() { const schema = new Schema({ div: { type: Number, required: function() { return false; }, validate: function(v) { return !!v; } } }); const Model = db.model('Test', schema); const m = new Model(); assert.ifError(m.validateSync()); }); it('ability to overwrite array default (gh-4109)', async function() { const schema = new Schema({ names: { type: [String], default: void 0 } }); const Model = db.model('Test', schema); const m = new Model(); assert.ok(!m.names); await m.save(); const doc = await Model.collection.findOne({ _id: m._id }); assert.ok(!('names' in doc)); }); it('validation works when setting array index (gh-3816)', async function() { const mySchema = new mongoose.Schema({ items: [ { month: Number, date: Date } ] }); const Test = db.model('test', mySchema); const a = [ { month: 0, date: new Date() }, { month: 1, date: new Date() } ]; const doc = await Test.create({ items: a }); const foundDoc = await Test.findById(doc._id).exec(); assert.ok(foundDoc); foundDoc.items[0] = { month: 5, date: new Date() }; foundDoc.markModified('items'); // Should not throw await foundDoc.save(); }); it('validateSync works when setting array index nested (gh-5389)', async function() { const childSchema = new mongoose.Schema({ _id: false, name: String, age: Number }); const schema = new mongoose.Schema({ name: String, children: [childSchema] }); const Model = db.model('Test', schema); const doc = await Model.create({ name: 'test', children: [ { name: 'test-child', age: 24 } ] }); const foundDoc = await Model.findById(doc._id); foundDoc.children[0] = { name: 'updated-child', age: 53 }; const errors = foundDoc.validateSync(); assert.ok(!errors); }); it('single embedded with defaults have $parent (gh-4115)', function() { const ChildSchema = new Schema({ name: { type: String, default: 'child' } }); const ParentSchema = new Schema({ child: { type: ChildSchema, default: {} } }); const Parent = db.model('Parent', ParentSchema); const p = new Parent(); assert.equal(p.child.$parent(), p); }); it('removing parent doc calls remove hooks on subdocs (gh-2348) (gh-4566)', async function() { const ChildSchema = new Schema({ name: String }); const called = {}; ChildSchema.pre('remove', function(next) { called[this.name] = true; next(); }); const ParentSchema = new Schema({ children: [ChildSchema], child: ChildSchema }); const Parent = db.model('Parent', ParentSchema); const doc = await Parent.create({ children: [{ name: 'Jacen' }, { name: 'Jaina' }], child: { name: 'Anakin' } }); await doc.remove(); assert.deepEqual(called, { Jacen: true, Jaina: true, Anakin: true }); const arr = doc.children.toObject().map(function(v) { return v.name; }); assert.deepEqual(arr, ['Jacen', 'Jaina']); assert.equal(doc.child.name, 'Anakin'); }); it('strings of length 12 are valid oids (gh-3365)', async function() { const schema = new Schema({ myId: mongoose.Schema.Types.ObjectId }); const M = db.model('Test', schema); const doc = new M({ myId: 'blablablabla' }); await doc.validate(); }); it('set() empty obj unmodifies subpaths (gh-4182)', async function() { const omeletteSchema = new Schema({ topping: { meat: { type: String, enum: ['bacon', 'sausage'] }, cheese: Boolean } }); const Omelette = db.model('Test', omeletteSchema); const doc = new Omelette({ topping: { meat: 'bacon', cheese: true } }); doc.topping = {}; await doc.save(); assert.strictEqual(doc.topping.meat, void 0); }); it('emits cb errors on model for save (gh-3499)', function(done) { const testSchema = new Schema({ name: String }); const Test = db.model('Test', testSchema); Test.on('error', function(error) { assert.equal(error.message, 'fail!'); done(); }); new Test({}).save(function() { throw new Error('fail!'); }); }); it('emits cb errors on model for save with hooks (gh-3499)', function(done) { const testSchema = new Schema({ name: String }); testSchema.pre('save', function(next) { next(); }); testSchema.post('save', function(doc, next) { next(); }); const Test = db.model('Test', testSchema); Test.on('error', function(error) { assert.equal(error.message, 'fail!'); done(); }); new Test({}).save(function() { throw new Error('fail!'); }); }); it('emits cb errors on model for find() (gh-3499)', function(done) { const testSchema = new Schema({ name: String }); const Test = db.model('Test', testSchema); Test.on('error', function(error) { assert.equal(error.message, 'fail!'); done(); }); Test.find({}, function() { throw new Error('fail!'); }); }); it('emits cb errors on model for find() + hooks (gh-3499)', function(done) { const testSchema = new Schema({ name: String }); testSchema.post('find', function(results, next) { assert.equal(results.length, 0); next(); }); const Test = db.model('Test', testSchema); Test.on('error', function(error) { assert.equal(error.message, 'fail!'); done(); }); Test.find({}, function() { throw new Error('fail!'); }); }); it('clears subpaths when removing single nested (gh-4216)', function(done) { const RecurrenceSchema = new Schema({ frequency: Number, interval: { type: String, enum: ['days', 'weeks', 'months', 'years'] } }, { _id: false }); const EventSchema = new Schema({ name: { type: String, trim: true }, recurrence: RecurrenceSchema }); const Event = db.model('Test', EventSchema); const ev = new Event({ name: 'test', recurrence: { frequency: 2, interval: 'days' } }); ev.recurrence = null; ev.save(function(error) { assert.ifError(error); done(); }); }); it('setting path to empty object works (gh-4218)', async function() { const schema = new Schema({ object: { nested: { field1: { type: Number, default: 1 } } } }); const MyModel = db.model('Test', schema);
let doc = await MyModel.create({}); doc.object.nested = {}; await doc.save(); doc = await MyModel.collection.findOne({ _id: doc._id }); assert.deepEqual(doc.object.nested, {}); }); it('setting path to object with strict and no paths in the schema (gh-6436) (gh-4218)', async function() { const schema = new Schema({ object: { nested: { field1: { type: Number, default: 1 } } } }); const MyModel = db.model('Test', schema);
let doc = await MyModel.create({}); doc.object.nested = { field2: 'foo' }; // `field2` not in the schema await doc.save(); doc = await MyModel.collection.findOne({ _id: doc._id }); assert.deepEqual(doc.object.nested, {}); }); it('minimize + empty object (gh-4337)', function() { const SomeModelSchema = new mongoose.Schema( {}, { minimize: false } ); const SomeModel = db.model('Test', SomeModelSchema); assert.doesNotThrow(function() { new SomeModel({}); }); }); it('directModifiedPaths() (gh-7373)', async function() { const schema = new Schema({ foo: String, nested: { bar: String } }); const Model = db.model('Test', schema);
await Model.create({ foo: 'original', nested: { bar: 'original' } }); const doc = await Model.findOne(); doc.nested.bar = 'modified'; assert.deepEqual(doc.directModifiedPaths(), ['nested.bar']); assert.deepEqual(doc.modifiedPaths().sort(), ['nested', 'nested.bar']); }); describe('modifiedPaths', function() { it('doesnt markModified child paths if parent is modified (gh-4224)', async function() { const childSchema = new Schema({ name: String }); const parentSchema = new Schema({ child: childSchema }); const Parent = db.model('Test', parentSchema); const doc = await Parent.create({ child: { name: 'Jacen' } }); doc.child = { name: 'Jaina' }; doc.child.name = 'Anakin'; assert.deepEqual(doc.modifiedPaths(), ['child']); assert.ok(doc.isModified('child.name')); }); it('includeChildren option (gh-6134)', function() { const personSchema = new mongoose.Schema({ name: { type: String }, colors: { primary: { type: String, default: 'white', enum: ['blue', 'green', 'red', 'purple', 'yellow'] } } }); const Person = db.model('Person', personSchema); const luke = new Person({ name: 'Luke', colors: { primary: 'blue' } }); assert.deepEqual(luke.modifiedPaths(), ['name', 'colors']); const obiwan = new Person({ name: 'Obi-Wan' }); obiwan.colors.primary = 'blue'; assert.deepEqual(obiwan.modifiedPaths(), ['name', 'colors', 'colors.primary']); const anakin = new Person({ name: 'Anakin' }); anakin.colors = { primary: 'blue' }; assert.deepEqual(anakin.modifiedPaths({ includeChildren: true }), ['name', 'colors', 'colors.primary']); }); it('includeChildren option with arrays (gh-5904)', function() { const teamSchema = new mongoose.Schema({ name: String, colors: { primary: { type: String, enum: ['blue', 'green', 'red', 'purple', 'yellow', 'white', 'black'] } }, members: [{ name: String }] }); const Team = db.model('Team', teamSchema); const jedis = new Team({ name: 'Jedis', colors: { primary: 'blue' }, members: [{ name: 'luke' }] }); const paths = jedis.modifiedPaths({ includeChildren: true }); assert.deepEqual(paths, [ 'name', 'colors', 'colors.primary', 'members', 'members.0', 'members.0.name' ]); }); it('1 level down nested paths get marked modified on initial set (gh-7313) (gh-6944)', function() { const testSchema = new Schema({ name: { first: String, last: String }, relatives: { aunt: { name: String }, uncle: { name: String } } }); const M = db.model('Test', testSchema); const doc = new M({ name: { first: 'A', last: 'B' }, relatives: { aunt: { name: 'foo' }, uncle: { name: 'bar' } } }); assert.ok(doc.modifiedPaths().indexOf('name') !== -1); assert.ok(doc.modifiedPaths().indexOf('relatives') !== -1); assert.ok(doc.modifiedPaths({ includeChildren: true }).indexOf('name.first') !== -1); assert.ok(doc.modifiedPaths({ includeChildren: true }).indexOf('name.last') !== -1); assert.ok(doc.modifiedPaths({ includeChildren: true }).indexOf('relatives.aunt') !== -1); assert.ok(doc.modifiedPaths({ includeChildren: true }).indexOf('relatives.uncle') !== -1); return Promise.resolve(); }); }); it('single nested isNew (gh-4369)', function(done) { const childSchema = new Schema({ name: String }); const parentSchema = new Schema({ child: childSchema }); const Parent = db.model('Test', parentSchema); let remaining = 2; const doc = new Parent({ child: { name: 'Jacen' } }); doc.child.on('isNew', function(val) { assert.ok(!val); assert.ok(!doc.child.isNew); --remaining || done(); }); doc.save(function(error, doc) { assert.ifError(error); assert.ok(!doc.child.isNew); --remaining || done(); }); }); it('deep default array values (gh-4540)', function() { const schema = new Schema({ arr: [{ test: { type: Array, default: ['test'] } }] }); assert.doesNotThrow(function() { db.model('Test', schema); }); }); it('default values with subdoc array (gh-4390)', function(done) { const childSchema = new Schema({ name: String }); const parentSchema = new Schema({ child: [childSchema] }); parentSchema.path('child').default([{ name: 'test' }]); const Parent = db.model('Parent', parentSchema); Parent.create({}, function(error, doc) { assert.ifError(error); const arr = doc.toObject().child.map(function(doc) { assert.ok(doc._id); delete doc._id; return doc; }); assert.deepEqual(arr, [{ name: 'test' }]); done(); }); }); it('handles invalid dates (gh-4404)', function(done) { const testSchema = new Schema({ date: Date }); const Test = db.model('Test', testSchema); Test.create({ date: new Date('invalid date') }, function(error) { assert.ok(error); assert.equal(error.errors['date'].name, 'CastError'); done(); }); }); it('setting array subpath (gh-4472)', function() { const ChildSchema = new mongoose.Schema({ name: String, age: Number }, { _id: false }); const ParentSchema = new mongoose.Schema({ data: { children: [ChildSchema] } }); const Parent = db.model('Parent', ParentSchema); const p = new Parent(); p.set('data.children.0', { name: 'Bob', age: 900 }); assert.deepEqual(p.toObject().data.children, [{ name: 'Bob', age: 900 }]); }); it('ignore paths (gh-4480)', async function() { const TestSchema = new Schema({ name: { type: String, required: true } }); const Test = db.model('Parent', TestSchema);
await Test.create({ name: 'val' }); let doc = await Test.findOne(); doc.name = null; doc.$ignore('name'); await doc.save(); doc = await Test.findById(doc._id); assert.equal(doc.name, 'val'); }); it('ignore subdocs paths (gh-4480) (gh-6152)', async function() { const childSchema = new Schema({ name: { type: String, required: true } }); const testSchema = new Schema({ child: childSchema, children: [childSchema] }); const Test = db.model('Test', testSchema);
await Test.create({ child: { name: 'testSingle' }, children: [{ name: 'testArr' }] }); let doc = await Test.findOne(); doc.child.name = null; doc.child.$ignore('name'); await doc.save(); doc = await Test.findById(doc._id); assert.equal(doc.child.name, 'testSingle'); doc.children[0].name = null; doc.children[0].$ignore('name'); await doc.save(); doc = await Test.findById(doc._id); assert.equal(doc.children[0].name, 'testArr'); }); it('composite _ids (gh-4542)', function(done) { const schema = new Schema({ _id: { key1: String, key2: String }, content: String }); const Model = db.model('Test', schema); const object = new Model(); object._id = { key1: 'foo', key2: 'bar' }; object.save(). then(function(obj) { obj.content = 'Hello'; return obj.save(); }). then(function(obj) { return Model.findOne({ _id: obj._id }); }). then(function(obj) { assert.equal(obj.content, 'Hello'); done(); }). catch(done); }); it('validateSync with undefined and conditional required (gh-4607)', function() { const schema = new mongoose.Schema({ type: mongoose.SchemaTypes.Number, conditional: { type: mongoose.SchemaTypes.String, required: function() { return this.type === 1; }, maxlength: 128 } }); const Model = db.model('Test', schema); assert.doesNotThrow(function() { new Model({ type: 2, conditional: void 0 }).validateSync(); }); }); it('conditional required on single nested (gh-4663)', function() { const childSchema = new Schema({ name: String }); const schema = new Schema({ child: { type: childSchema, required: function() { assert.equal(this.child.name, 'test'); } } }); const M = db.model('Test', schema); const err = new M({ child: { name: 'test' } }).validateSync(); assert.ifError(err); }); it('setting full path under single nested schema works (gh-4578) (gh-4528)', function(done) { const ChildSchema = new mongoose.Schema({ age: Number }); const ParentSchema = new mongoose.Schema({ age: Number, family: { child: ChildSchema } }); const M = db.model('Test', ParentSchema); M.create({ age: 45 }, function(error, doc) { assert.ifError(error); assert.ok(!doc.family.child); doc.set('family.child.age', 15); assert.ok(doc.family.child.schema); assert.ok(doc.isModified('family.child')); assert.ok(doc.isModified('family.child.age')); assert.equal(doc.family.child.toObject().age, 15); done(); }); }); it('setting a nested path retains nested modified paths (gh-5206)', async function() { const testSchema = new mongoose.Schema({ name: String, surnames: { docarray: [{ name: String }] } }); const Cat = db.model('Cat', testSchema); const kitty = new Cat({ name: 'Test', surnames: { docarray: [{ name: 'test1' }, { name: 'test2' }] } }); await kitty.save(); kitty.surnames = { docarray: [{ name: 'test1' }, { name: 'test2' }, { name: 'test3' }] }; assert.deepEqual( kitty.modifiedPaths(), ['surnames', 'surnames.docarray'] ); }); it('toObject() does not depopulate top level (gh-3057)', function() { const Cat = db.model('Cat', { name: String }); const Human = db.model('Person', { name: String, petCat: { type: mongoose.Schema.Types.ObjectId, ref: 'Cat' } }); const kitty = new Cat({ name: 'Zildjian' }); const person = new Human({ name: 'Val', petCat: kitty }); assert.equal(kitty.toObject({ depopulate: true }).name, 'Zildjian'); assert.ok(!person.toObject({ depopulate: true }).petCat.name); }); it('toObject() respects schema-level depopulate (gh-6313)', function() { const personSchema = Schema({ name: String, car: { type: Schema.Types.ObjectId, ref: 'Car' } }); personSchema.set('toObject', { depopulate: true }); const carSchema = Schema({ name: String }); const Car = db.model('Car', carSchema); const Person = db.model('Person', personSchema); const car = new Car({ name: 'Ford' }); const person = new Person({ name: 'John', car: car }); assert.equal(person.toObject().car.toHexString(), car._id.toHexString()); }); it('single nested doc conditional required (gh-4654)', function(done) { const ProfileSchema = new Schema({ firstName: String, lastName: String }); function validator() { assert.equal(this.email, 'test'); return true; } const UserSchema = new Schema({ email: String, profile: { type: ProfileSchema, required: [validator, 'profile required'] } }); const User = db.model('User', UserSchema); User.create({ email: 'test' }, function(error) { assert.equal(error.errors['profile'].message, 'profile required'); done(); }); }); it('handles setting single nested schema to equal value (gh-4676)', function(done) { const companySchema = new mongoose.Schema({ _id: false, name: String, description: String }); const userSchema = new mongoose.Schema({ name: String, company: companySchema }); const User = db.model('User', userSchema); const user = new User({ company: { name: 'Test' } }); user.save(function(error) { assert.ifError(error); user.company.description = 'test'; assert.ok(user.isModified('company')); user.company = user.company; assert.ok(user.isModified('company')); done(); }); }); it('handles setting single nested doc to null after setting (gh-4766)', function(done) { const EntitySchema = new Schema({ company: { type: String, required: true }, name: { type: String, required: false }, email: { type: String, required: false } }, { _id: false, id: false }); const ShipmentSchema = new Schema({ entity: { shipper: { type: EntitySchema, required: false }, manufacturer: { type: EntitySchema, required: false } } }); const Shipment = db.model('Test', ShipmentSchema); const doc = new Shipment({ entity: { shipper: null, manufacturer: { company: 'test', name: 'test', email: 'test@email' } } }); doc.save(). then(function() { return Shipment.findById(doc._id); }). then(function(shipment) { shipment.entity = shipment.entity; shipment.entity.manufacturer = null; return shipment.save(); }). then(function() { done(); }). catch(done); }); it('buffers with subtypes as ids (gh-4506)', function(done) { const uuid = require('uuid'); const UserSchema = new mongoose.Schema({ _id: { type: Buffer, default: function() { return mongoose.Types.Buffer(uuid.parse(uuid.v4())).toObject(4); }, required: true }, email: { type: String, lowercase: true, required: true }, name: String }); const User = db.model('User', UserSchema); const user = new User({ email: 'me@email.com', name: 'My name' }); user.save(). then(function() { return User.findOne({ email: 'me@email.com' }); }). then(function(user) { user.name = 'other'; return user.save(); }). then(function() { return User.findOne({ email: 'me@email.com' }); }). then(function(doc) { assert.equal(doc.name, 'other'); done(); }). catch(done); }); it('embedded docs dont mark parent as invalid (gh-4681)', function(done) { const NestedSchema = new mongoose.Schema({ nestedName: { type: String, required: true }, createdAt: { type: Date, required: true } }); const RootSchema = new mongoose.Schema({ rootName: String, nested: { type: [NestedSchema] } }); const Root = db.model('Test', RootSchema); const root = new Root({ rootName: 'root', nested: [{ }] }); root.save(function(error) { assert.ok(error); assert.deepEqual(Object.keys(error.errors).sort(), ['nested.0.createdAt', 'nested.0.nestedName']); done(); }); }); it('should depopulate the shard key when saving (gh-4658)', function(done) { const ChildSchema = new mongoose.Schema({ name: String }); const ChildModel = db.model('Child', ChildSchema); const ParentSchema = new mongoose.Schema({ name: String, child: { type: Schema.Types.ObjectId, ref: 'Child' } }, { shardKey: { child: 1, _id: 1 } }); const ParentModel = db.model('Parent', ParentSchema); ChildModel.create({ name: 'Luke' }). then(function(child) { const p = new ParentModel({ name: 'Vader' }); p.child = child; return p.save(); }). then(function(p) { p.name = 'Anakin'; return p.save(); }). then(function(p) { return ParentModel.findById(p); }). then(function(doc) { assert.equal(doc.name, 'Anakin'); done(); }). catch(done); }); it('handles setting virtual subpaths (gh-4716)', function() { const childSchema = new Schema({ name: { type: String, default: 'John' }, favorites: { color: { type: String, default: 'Blue' } } }); const parentSchema = new Schema({ name: { type: String }, children: { type: [childSchema], default: [{}] } }); parentSchema.virtual('favorites').set(function(v) { return this.children[0].set('favorites', v); }).get(function() { return this.children[0].get('favorites'); }); const Parent = db.model('Parent', parentSchema); const p = new Parent({ name: 'Anakin' }); p.set('children.0.name', 'Leah'); p.set('favorites.color', 'Red'); assert.equal(p.children[0].favorites.color, 'Red'); }); it('handles selected nested elements with defaults (gh-4739) (gh-11376)', async function() { const userSchema = new Schema({ preferences: { sleep: { type: Boolean, default: false }, test: { type: Boolean, default: true } }, arr: [{ test: Number, test2: Number }], name: String }); const User = db.model('User', userSchema); let user = { name: 'test' }; await User.collection.insertOne(user); user = await User.findById(user, { 'preferences.sleep': 1, name: 1 }); assert.strictEqual(user.preferences.sleep, false); assert.ok(!user.preferences.test); user = await User.findById(user, { 'arr.test': 1 }); assert.strictEqual(user.name, undefined); assert.strictEqual(user.toObject().preferences, undefined); assert.deepEqual(user.toObject().arr, []); }); it('handles mark valid in subdocs correctly (gh-4778)', function() { const SubSchema = new mongoose.Schema({ field: { nestedField: { type: mongoose.Schema.ObjectId, required: false } } }, { _id: false, id: false }); const Model2Schema = new mongoose.Schema({ sub: { type: SubSchema, required: false } }); const Model2 = db.model('Test', Model2Schema); const doc = new Model2({ sub: {} }); doc.sub.field.nestedField = { }; doc.sub.field.nestedField = '574b69d0d9daf106aaa62974'; assert.ok(!doc.validateSync()); }); it('timestamps set to false works (gh-7074)', async function() { const schema = new Schema({ name: String }, { timestamps: false }); const Test = db.model('Test', schema); const doc = await Test.create({ name: 'test' }); assert.strictEqual(doc.updatedAt, undefined); assert.strictEqual(doc.createdAt, undefined); }); it('timestamps with nested paths (gh-5051)', function(done) { const schema = new Schema({ props: {} }, { timestamps: { createdAt: 'props.createdAt', updatedAt: 'props.updatedAt' } }); const M = db.model('Test', schema); const now = Date.now(); M.create({}, function(error, doc) { assert.ok(doc.props.createdAt); assert.ok(doc.props.createdAt instanceof Date); assert.ok(doc.props.createdAt.valueOf() >= now); assert.ok(doc.props.updatedAt); assert.ok(doc.props.updatedAt instanceof Date); assert.ok(doc.props.updatedAt.valueOf() >= now); done(); }); }); it('Declaring defaults in your schema with timestamps defined (gh-6024)', function() { const schemaDefinition = { name: String, misc: { hometown: String, isAlive: { type: Boolean, default: true } } }; const schemaWithTimestamps = new Schema(schemaDefinition, { timestamps: { createdAt: 'misc.createdAt' } }); const PersonWithTimestamps = db.model('Person', schemaWithTimestamps); const dude = new PersonWithTimestamps({ name: 'Keanu', misc: { hometown: 'Beirut' } }); assert.equal(dude.misc.isAlive, true); }); it('supports $where in pre save hook (gh-4004)', function(done) { const Promise = global.Promise; const schema = new Schema({ name: String }, { timestamps: true, versionKey: null }); schema.pre('save', function(next) { this.$where = { updatedAt: this.updatedAt }; next(); }); schema.post('save', function(error, res, next) { assert.ok(error instanceof MongooseError.DocumentNotFoundError); assert.ok(error.message.indexOf('Test') !== -1, error.message); error = new Error('Somebody else updated the document!'); next(error); }); const MyModel = db.model('Test', schema); MyModel.create({ name: 'test' }). then(function() { return Promise.all([ MyModel.findOne(), MyModel.findOne() ]); }). then(function(docs) { docs[0].name = 'test2'; return Promise.all([ docs[0].save(), Promise.resolve(docs[1]) ]); }). then(function(docs) { docs[1].name = 'test3'; return docs[1].save(); }). then(function() { done(new Error('Should not get here')); }). catch(function(error) { assert.equal(error.message, 'Somebody else updated the document!'); done(); }); }); it('toObject() with buffer and minimize (gh-4800)', function(done) { const TestSchema = new mongoose.Schema({ buf: Buffer }, { toObject: { virtuals: true, getters: true } }); const Test = db.model('Test', TestSchema); Test.create({ buf: Buffer.from('abcd') }). then(function(doc) { return Test.findById(doc._id); }). then(function(doc) { assert.doesNotThrow(function() { require('util').inspect(doc); }); done(); }). catch(done); }); it('buffer subtype prop (gh-5530)', function() { const TestSchema = new mongoose.Schema({ uuid: { type: Buffer, subtype: 4 } }); const Test = db.model('Test', TestSchema); const doc = new Test({ uuid: 'test1' }); assert.equal(doc.uuid._subtype, 4); }); it('runs validate hooks on single nested subdocs if not directly modified (gh-3884)', function(done) { const childSchema = new Schema({ name: { type: String }, friends: [{ type: String }] }); let count = 0; childSchema.pre('validate', function(next) { ++count; next(); }); const parentSchema = new Schema({ name: { type: String }, child: childSchema }); const Parent = db.model('Parent', parentSchema); const p = new Parent({ name: 'Mufasa', child: { name: 'Simba', friends: ['Pumbaa', 'Timon', 'Nala'] } }); p.save(). then(function(p) { assert.equal(count, 1); p.child.friends.push('Rafiki'); return p.save(); }). then(function() { assert.equal(count, 2); done(); }). catch(done); }); it('runs validate hooks on arrays subdocs if not directly modified (gh-5861)', function(done) { const childSchema = new Schema({ name: { type: String }, friends: [{ type: String }] }); let count = 0; childSchema.pre('validate', function(next) { ++count; next(); }); const parentSchema = new Schema({ name: { type: String }, children: [childSchema] }); const Parent = db.model('Parent', parentSchema); const p = new Parent({ name: 'Mufasa', children: [{ name: 'Simba', friends: ['Pumbaa', 'Timon', 'Nala'] }] }); p.save(). then(function(p) { assert.equal(count, 1); p.children[0].friends.push('Rafiki'); return p.save(); }). then(function() { assert.equal(count, 2); done(); }). catch(done); }); it('does not run schema type validator on single nested if not direct modified (gh-5885)', async function() { let childValidateCalls = 0; const childSchema = new Schema({ name: String, otherProp: { type: String, validate: () => { ++childValidateCalls; return true; } } }); let validateCalls = 0; const parentSchema = new Schema({ child: { type: childSchema, validate: () => { ++validateCalls; return true; } } });
const Parent = db.model('Parent', parentSchema); const doc = await Parent.create({ child: { name: 'test', otherProp: 'test' } }); assert.equal(childValidateCalls, 1); assert.equal(validateCalls, 1); childValidateCalls = 0; validateCalls = 0; doc.set('child.name', 'test2'); await doc.validate(); assert.equal(childValidateCalls, 0); assert.equal(validateCalls, 0); }); it('runs schema type validator on single nested if parent has default (gh-7493)', function() { const childSchema = new Schema({ test: String }); const parentSchema = new Schema({ child: { type: childSchema, default: {}, validate: () => false } }); const Parent = db.model('Test', parentSchema); const parentDoc = new Parent({}); parentDoc.child.test = 'foo'; const err = parentDoc.validateSync(); assert.ok(err); assert.ok(err.errors['child']); return Promise.resolve(); }); it('does not overwrite when setting nested (gh-4793)', function() { const grandchildSchema = new mongoose.Schema(); grandchildSchema.method({ foo: function() { return 'bar'; } }); const Grandchild = db.model('Test', grandchildSchema); const childSchema = new mongoose.Schema({ grandchild: grandchildSchema }); const Child = db.model('Child', childSchema); const parentSchema = new mongoose.Schema({ children: [childSchema] }); const Parent = db.model('Parent', parentSchema); const grandchild = new Grandchild(); const child = new Child({ grandchild: grandchild }); assert.equal(child.grandchild.foo(), 'bar'); const p = new Parent({ children: [child] }); assert.equal(child.grandchild.foo(), 'bar'); assert.equal(p.children[0].grandchild.foo(), 'bar'); }); it('hooks/middleware for custom methods (gh-6385) (gh-7456)', async function() { const mySchema = new Schema({ name: String }); mySchema.methods.foo = function(cb) { return cb(null, this.name); }; mySchema.methods.bar = function() { return this.name; }; mySchema.methods.baz = function(arg) { return Promise.resolve(arg); }; let preFoo = 0; let postFoo = 0; mySchema.pre('foo', function() { ++preFoo; }); mySchema.post('foo', function() { ++postFoo; }); let preBaz = 0; let postBaz = 0; mySchema.pre('baz', function() { ++preBaz; }); mySchema.post('baz', function() { ++postBaz; }); const MyModel = db.model('Test', mySchema);
const doc = new MyModel({ name: 'test' }); assert.equal(doc.bar(), 'test'); assert.equal(preFoo, 0); assert.equal(postFoo, 0); const fooResult = await doc.foo(); assert.equal(fooResult, 'test'); assert.equal(preFoo, 1); assert.equal(postFoo, 1); assert.equal(preBaz, 0); assert.equal(postBaz, 0); assert.equal(await doc.baz('foobar'), 'foobar'); assert.equal(preBaz, 1); assert.equal(preBaz, 1); }); it('custom methods with promises (gh-6385)', async function() { const mySchema = new Schema({ name: String }); mySchema.methods.foo = function() { return Promise.resolve(this.name + ' foo'); }; mySchema.methods.bar = function() { return this.name + ' bar'; }; let preFoo = 0; let preBar = 0; mySchema.pre('foo', function() { ++preFoo; }); mySchema.pre('bar', function() { ++preBar; }); const MyModel = db.model('Test', mySchema);
const doc = new MyModel({ name: 'test' }); assert.equal(preFoo, 0); assert.equal(preBar, 0); let foo = doc.foo(); let bar = doc.bar(); assert.ok(foo instanceof Promise); assert.ok(bar instanceof Promise); foo = await foo; bar = await bar; assert.equal(preFoo, 1); assert.equal(preBar, 1); assert.equal(foo, 'test foo'); assert.equal(bar, 'test bar'); }); it('toString() as custom method (gh-6538)', function() { const commentSchema = new Schema({ title: String }); commentSchema.methods.toString = function() { return `${this.constructor.modelName}(${this.title})`; }; const Comment = db.model('Comment', commentSchema); const c = new Comment({ title: 'test' }); assert.strictEqual('Comment(test)', `${c}`); }); it('setting to discriminator (gh-4935)', function() { const Buyer = db.model('Test1', new Schema({ name: String, vehicle: { type: Schema.Types.ObjectId, ref: 'Test' } })); const Vehicle = db.model('Test', new Schema({ name: String })); const Car = Vehicle.discriminator('gh4935_1', new Schema({ model: String })); const eleanor = new Car({ name: 'Eleanor', model: 'Shelby Mustang GT' }); const nick = new Buyer({ name: 'Nicolas', vehicle: eleanor }); assert.ok(!!nick.vehicle); assert.ok(nick.vehicle === eleanor); assert.ok(nick.vehicle instanceof Car); assert.equal(nick.vehicle.name, 'Eleanor'); }); it('handles errors in sync validators (gh-2185)', function(done) { const schema = new Schema({ name: { type: String, validate: function() { throw new Error('woops!'); } } }); const M = db.model('Test', schema); const error = (new M({ name: 'test' })).validateSync(); assert.ok(error); assert.equal(error.errors['name'].reason.message, 'woops!'); new M({ name: 'test' }).validate(function(error) { assert.ok(error); assert.equal(error.errors['name'].reason.message, 'woops!'); done(); }); }); it('allows hook as a schema key (gh-5047)', function(done) { const schema = new mongoose.Schema({ name: String, hook: { type: String } }); const Model = db.model('Test', schema); Model.create({ hook: 'test ' }, function(error) { assert.ifError(error); done(); }); }); it('save errors with callback and promise work (gh-5216)', function(done) { const schema = new mongoose.Schema({}); const Model = db.model('Test', schema); const _id = new mongoose.Types.ObjectId(); const doc1 = new Model({ _id: _id }); const doc2 = new Model({ _id: _id }); let remaining = 2; Model.on('error', function(error) { assert.ok(error); --remaining || done(); }); doc1.save(). then(function() { return doc2.save(); }). catch(function(error) { assert.ok(error); --remaining || done(); }); }); it('post hooks on child subdocs run after save (gh-5085)', function(done) { const ChildModelSchema = new mongoose.Schema({ text: { type: String } }); ChildModelSchema.post('save', function(doc) { doc.text = 'bar'; }); const ParentModelSchema = new mongoose.Schema({ children: [ChildModelSchema] }); const Model = db.model('Parent', ParentModelSchema); Model.create({ children: [{ text: 'test' }] }, function(error) { assert.ifError(error); Model.findOne({}, function(error, doc) { assert.ifError(error); assert.equal(doc.children.length, 1); assert.equal(doc.children[0].text, 'test'); done(); }); }); }); it('post hooks on array child subdocs run after save (gh-5085) (gh-6926)', function() { const subSchema = new Schema({ val: String }); subSchema.post('save', function() { return Promise.reject(new Error('Oops')); }); const schema = new Schema({ sub: subSchema }); const Test = db.model('Test', schema); const test = new Test({ sub: { val: 'test' } }); return test.save(). then(() => assert.ok(false), err => assert.equal(err.message, 'Oops')). then(() => Test.findOne()). then(doc => assert.equal(doc.sub.val, 'test')); }); it('nested docs toObject() clones (gh-5008)', function() { const schema = new mongoose.Schema({ sub: { height: Number } }); const Model = db.model('Test', schema); const doc = new Model({ sub: { height: 3 } }); assert.equal(doc.sub.height, 3); const leanDoc = doc.sub.toObject(); assert.equal(leanDoc.height, 3); doc.sub.height = 55; assert.equal(doc.sub.height, 55); assert.equal(leanDoc.height, 3); }); it('toObject() with null (gh-5143)', function() { const schema = new mongoose.Schema({ customer: { name: { type: String, required: false } } }); const Model = db.model('Test', schema); const model = new Model(); model.customer = null; assert.strictEqual(model.toObject().customer, null); assert.strictEqual(model.toObject({ getters: true }).customer, null); }); it('handles array subdocs with single nested subdoc default (gh-5162)', function() { const RatingsItemSchema = new mongoose.Schema({ value: Number }, { versionKey: false, _id: false }); const RatingsSchema = new mongoose.Schema({ ratings: { type: RatingsItemSchema, default: { id: 1, value: 0 } }, _id: false }); const RestaurantSchema = new mongoose.Schema({ menu: { type: [RatingsSchema] } }); const Restaurant = db.model('Test', RestaurantSchema); // Should not throw const r = new Restaurant(); assert.deepEqual(r.toObject().menu, []); }); it('iterating through nested doc keys (gh-5078)', function() { const schema = new Schema({ nested: { test1: String, test2: String } }); schema.virtual('tests').get(function() { return Object.values(this.nested); }); const M = db.model('Test', schema); const doc = new M({ nested: { test1: 'a', test2: 'b' } }); assert.deepEqual(doc.toObject({ virtuals: true }).tests, ['a', 'b']); assert.doesNotThrow(function() { require('util').inspect(doc); }); JSON.stringify(doc); }); it('deeply nested virtual paths (gh-5250)', function() { const TestSchema = new Schema({}); TestSchema. virtual('a.b.c'). get(function() { return this.v; }). set(function(value) { this.v = value; }); const TestModel = db.model('Test', TestSchema); const t = new TestModel({ 'a.b.c': 5 }); assert.equal(t.a.b.c, 5); }); it('nested virtual when populating with parent projected out (gh-7491)', async function() { const childSchema = Schema({ _id: Number, nested: { childPath: String }, otherPath: String }, { toObject: { virtuals: true } }); childSchema.virtual('nested.childVirtual').get(() => true); const parentSchema = Schema({ child: { type: Number, ref: 'Child' } }, { toObject: { virtuals: true } }); parentSchema.virtual('_nested').get(function() { return this.child.nested; }); const Child = db.model('Child', childSchema); const Parent = db.model('Parent', parentSchema);
await Child.create({ _id: 1, nested: { childPath: 'foo' }, otherPath: 'bar' }); await Parent.create({ child: 1 }); const doc = await Parent.findOne().populate('child', 'otherPath'). then(doc => doc.toObject()); assert.ok(!doc.child.nested.childPath); }); it('JSON.stringify nested errors (gh-5208)', function(done) { const AdditionalContactSchema = new Schema({ contactName: { type: String, required: true }, contactValue: { type: String, required: true } }); const ContactSchema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true }, additionalContacts: [AdditionalContactSchema] }); const EmergencyContactSchema = new Schema({ contactName: { type: String, required: true }, contact: ContactSchema }); const EmergencyContact = db.model('Test', EmergencyContactSchema); const contact = new EmergencyContact({ contactName: 'Electrical Service', contact: { name: 'John Smith', email: 'john@gmail.com', additionalContacts: [ { contactName: 'skype' // Forgotten value } ] } }); contact.validate(function(error) { assert.ok(error); assert.ok(error.errors['contact.additionalContacts.0.contactValue']); // This `JSON.stringify()` should not throw assert.ok(JSON.stringify(error).indexOf('contactValue') !== -1); done(); }); }); it('handles errors in subdoc pre validate (gh-5215)', function(done) { const childSchema = new mongoose.Schema({}); childSchema.pre('validate', function(next) { next(new Error('child pre validate')); }); const parentSchema = new mongoose.Schema({ child: childSchema }); const Parent = db.model('Parent', parentSchema); Parent.create({ child: {} }, function(error) { assert.ok(error); assert.ok(error.errors['child']); assert.equal(error.errors['child'].message, 'child pre validate'); done(); }); }); it('custom error types (gh-4009)', function(done) { const CustomError = function() {}; const testSchema = new mongoose.Schema({ num: { type: Number, required: { ErrorConstructor: CustomError }, min: 5 } }); const Test = db.model('Test', testSchema); Test.create({}, function(error) { assert.ok(error); assert.ok(error.errors['num']); assert.ok(error.errors['num'] instanceof CustomError); Test.create({ num: 1 }, function(error) { assert.ok(error); assert.ok(error.errors['num']); assert.ok(error.errors['num'].constructor.name, 'ValidatorError'); assert.ok(!(error.errors['num'] instanceof CustomError)); done(); }); }); }); it('saving a doc with nested string array (gh-5282)', function(done) { const testSchema = new mongoose.Schema({ strs: [[String]] }); const Test = db.model('Test', testSchema); const t = new Test({ strs: [['a', 'b']] }); t.save(function(error, t) { assert.ifError(error); assert.deepEqual(t.toObject().strs, [['a', 'b']]); done(); }); }); it('push() onto a nested doc array (gh-6398)', async function() { const schema = new mongoose.Schema({ name: String, array: [[{ key: String, value: Number }]] }); const Model = db.model('Test', schema);
await Model.create({ name: 'small', array: [[{ key: 'answer', value: 42 }]] }); let doc = await Model.findOne(); assert.ok(doc); doc.array[0].push({ key: 'lucky', value: 7 }); await doc.save(); doc = await Model.findOne(); assert.equal(doc.array.length, 1); assert.equal(doc.array[0].length, 2); assert.equal(doc.array[0][1].key, 'lucky'); }); it('push() onto a triple nested doc array (gh-6602) (gh-6398)', async function() { const schema = new mongoose.Schema({ array: [[[{ key: String, value: Number }]]] }); const Model = db.model('Test', schema);
await Model.create({ array: [[[{ key: 'answer', value: 42 }]]] }); let doc = await Model.findOne(); assert.ok(doc); doc.array[0][0].push({ key: 'lucky', value: 7 }); await doc.save(); doc = await Model.findOne(); assert.equal(doc.array.length, 1); assert.equal(doc.array[0].length, 1); assert.equal(doc.array[0][0].length, 2); assert.equal(doc.array[0][0][1].key, 'lucky'); }); it('null _id (gh-5236)', function(done) { const childSchema = new mongoose.Schema({}); const M = db.model('Test', childSchema); const m = new M({ _id: null }); m.save(function(error, doc) { assert.equal(doc._id, null); done(); }); }); it('setting populated path with typeKey (gh-5313)', function() { const personSchema = Schema({ name: { $type: String }, favorite: { $type: Schema.Types.ObjectId, ref: 'Book' }, books: [{ $type: Schema.Types.ObjectId, ref: 'Book' }] }, { typeKey: '$type' }); const bookSchema = Schema({ title: String }); const Book = db.model('Book', bookSchema); const Person = db.model('Person', personSchema); const book1 = new Book({ title: 'The Jungle Book' }); const book2 = new Book({ title: '1984' }); const person = new Person({ name: 'Bob', favorite: book1, books: [book1, book2] }); assert.equal(person.books[0].title, 'The Jungle Book'); assert.equal(person.books[1].title, '1984'); }); it('save twice with write concern (gh-5294)', function(done) { const schema = new mongoose.Schema({ name: String }, { w: 'majority', wtimeout: 1e4 }); const M = db.model('Test', schema); M.create({ name: 'Test' }, function(error, doc) { assert.ifError(error); doc.name = 'test2'; doc.save(function(error) { assert.ifError(error); done(); }); }); }); it('undefined field with conditional required (gh-5296)', function(done) { const schema = Schema({ name: { type: String, maxlength: 63, required: function() { return false; } } }); const Model = db.model('Test', schema); Model.create({ name: undefined }, function(error) { assert.ifError(error); done(); }); }); it('dotted virtuals in toObject (gh-5473)', function() { const schema = new mongoose.Schema({}, { toObject: { virtuals: true }, toJSON: { virtuals: true } }); schema.virtual('test.a').get(function() { return 1; }); schema.virtual('test.b').get(function() { return 2; }); const Model = db.model('Test', schema); const m = new Model({}); assert.deepEqual(m.toJSON().test, { a: 1, b: 2 }); assert.deepEqual(m.toObject().test, { a: 1, b: 2 }); assert.equal(m.toObject({ virtuals: false }).test, void 0); }); it('dotted virtuals in toObject (gh-5506)', function(done) { const childSchema = new Schema({ name: String, _id: false }); const parentSchema = new Schema({ child: { type: childSchema, default: {} } }); const Parent = db.model('Parent', parentSchema); const p = new Parent({ child: { name: 'myName' } }); p.save(). then(function() { return Parent.findOne(); }). then(function(doc) { doc.child = {}; return doc.save(); }). then(function() { return Parent.findOne(); }). then(function(doc) { assert.deepEqual(doc.toObject({ minimize: false }).child, {}); done(); }). catch(done); }); it('parent props not in child (gh-5470)', function() { const employeeSchema = new mongoose.Schema({ name: { first: String, last: String }, department: String }); const Employee = db.model('Test', employeeSchema); const employee = new Employee({ name: { first: 'Ron', last: 'Swanson' }, department: 'Parks and Recreation' }); const ownPropertyNames = Object.getOwnPropertyNames(employee.name); assert.ok(ownPropertyNames.indexOf('department') === -1, ownPropertyNames.join(',')); assert.ok(ownPropertyNames.indexOf('first') !== -1, ownPropertyNames.join(',')); assert.ok(ownPropertyNames.indexOf('last') !== -1, ownPropertyNames.join(',')); }); it('modifying array with existing ids (gh-5523)', function(done) { const friendSchema = new mongoose.Schema( { _id: String, name: String, age: Number, dob: Date }, { _id: false }); const socialSchema = new mongoose.Schema( { friends: [friendSchema] }, { _id: false }); const userSchema = new mongoose.Schema({ social: { type: socialSchema, required: true } }); const User = db.model('User', userSchema); const user = new User({ social: { friends: [ { _id: 'val', age: 28 } ] } }); user.social.friends = [{ _id: 'val', name: 'Val' }]; assert.deepEqual(user.toObject().social.friends[0], { _id: 'val', name: 'Val' }); user.save(function(error) { assert.ifError(error); User.findOne({ _id: user._id }, function(error, doc) { assert.ifError(error); assert.deepEqual(doc.toObject().social.friends[0], { _id: 'val', name: 'Val' }); done(); }); }); }); it('consistent setter context for single nested (gh-5363)', function(done) { const contentSchema = new Schema({ blocks: [{ type: String }], previous: [{ type: String }] }); // Subdocument setter const oldVals = []; contentSchema.path('blocks').set(function(newVal, oldVal) { if (!this.ownerDocument().isNew && oldVal != null) { oldVals.push(oldVal.toObject()); this.set('previous', [].concat(oldVal.toObject())); } return newVal; }); const noteSchema = new Schema({ title: { type: String, required: true }, body: contentSchema }); const Note = db.model('Test', noteSchema); const note = new Note({ title: 'Lorem Ipsum Dolor', body: { summary: 'Summary Test', blocks: ['html'] } }); note.save(). then(function(note) { assert.equal(oldVals.length, 0); note.set('body', { blocks: ['gallery', 'html'] }); return note.save(); }). then(function() { assert.equal(oldVals.length, 1); assert.deepEqual(oldVals[0], ['html']); assert.deepEqual(note.body.previous, ['html']); done(); }). catch(done); }); it('deeply nested subdocs and markModified (gh-5406)', function(done) { const nestedValueSchema = new mongoose.Schema({ _id: false, value: Number }); const nestedPropertySchema = new mongoose.Schema({ _id: false, active: Boolean, nestedValue: nestedValueSchema }); const nestedSchema = new mongoose.Schema({ _id: false, nestedProperty: nestedPropertySchema, nestedTwoProperty: nestedPropertySchema }); const optionsSchema = new mongoose.Schema({ _id: false, nestedField: nestedSchema }); const TestSchema = new mongoose.Schema({ fieldOne: String, options: optionsSchema }); const Test = db.model('Test', TestSchema); const doc = new Test({ fieldOne: 'Test One', options: { nestedField: { nestedProperty: { active: true, nestedValue: { value: 42 } } } } }); doc. save(). then(function(doc) { doc.options.nestedField.nestedTwoProperty = { active: true, nestedValue: { value: 1337 } }; assert.ok(doc.isModified('options')); return doc.save(); }). then(function(doc) { return Test.findById(doc._id); }). then(function(doc) { assert.equal(doc.options.nestedField.nestedTwoProperty.nestedValue.value, 1337); done(); }). catch(done); }); it('single nested subdoc post remove hooks (gh-5388)', function(done) { const contentSchema = new Schema({ blocks: [{ type: String }], summary: { type: String } }); let called = 0; contentSchema.post('remove', function() { ++called; }); const noteSchema = new Schema({ body: { type: contentSchema } }); const Note = db.model('Test', noteSchema); const note = new Note({ title: 'Lorem Ipsum Dolor', body: { summary: 'Summary Test', blocks: ['html'] } }); note.save(function(error) { assert.ifError(error); note.remove(function(error) { assert.ifError(error); setTimeout(function() { assert.equal(called, 1); done(); }, 50); }); }); }); it('push populated doc onto empty array triggers manual population (gh-5504)', function() { const ReferringSchema = new Schema({ reference: [{ type: Schema.Types.ObjectId, ref: 'Test' }] }); const Referrer = db.model('Test', ReferringSchema); const referenceA = new Referrer(); const referenceB = new Referrer(); const referrerA = new Referrer({ reference: [referenceA] }); const referrerB = new Referrer(); const referrerC = new Referrer(); const referrerD = new Referrer(); const referrerE = new Referrer(); referrerA.reference.push(referenceB); assert.ok(referrerA.reference[0] instanceof Referrer); assert.ok(referrerA.reference[1] instanceof Referrer); referrerB.reference.push(referenceB); assert.ok(referrerB.reference[0] instanceof Referrer); referrerC.reference.unshift(referenceB); assert.ok(referrerC.reference[0] instanceof Referrer); referrerD.reference.splice(0, 0, referenceB); assert.ok(referrerD.reference[0] instanceof Referrer); referrerE.reference.addToSet(referenceB); assert.ok(referrerE.reference[0] instanceof Referrer); }); it('single nested conditional required scope (gh-5569)', function(done) { const scopes = []; const ThingSchema = new mongoose.Schema({ undefinedDisallowed: { type: String, required: function() { scopes.push(this); return this.undefinedDisallowed === undefined; }, default: null } }); const SuperDocumentSchema = new mongoose.Schema({ thing: { type: ThingSchema, default: function() { return {}; } } }); const SuperDocument = db.model('Test', SuperDocumentSchema); let doc = new SuperDocument(); doc.thing.undefinedDisallowed = null; doc.save(function(error) { assert.ifError(error); doc = new SuperDocument(); doc.thing.undefinedDisallowed = undefined; doc.save(function(error) { assert.ok(error); assert.ok(error.errors['thing.undefinedDisallowed']); done(); }); }); }); it('single nested setters only get called once (gh-5601)', function() { const vals = []; const ChildSchema = new mongoose.Schema({ number: { type: String, set: function(v) { vals.push(v); return v; } }, _id: false }); ChildSchema.set('toObject', { getters: true, minimize: false }); const ParentSchema = new mongoose.Schema({ child: { type: ChildSchema, default: {} } }); const Parent = db.model('Parent', ParentSchema); const p = new Parent(); p.child = { number: '555.555.0123' }; assert.equal(vals.length, 1); assert.equal(vals[0], '555.555.0123'); }); it('single getters only get called once (gh-7442)', function() { let called = 0; const childSchema = new Schema({ value: { type: String, get: function(v) { ++called; return v; } } }); const schema = new Schema({ name: childSchema }); const Model = db.model('Test', schema); const doc = new Model({ 'name.value': 'test' }); called = 0; doc.toObject({ getters: true }); assert.equal(called, 1); doc.toObject({ getters: false }); assert.equal(called, 1); }); it('calls subdocument getters if child schema has getters: true (gh-12105)', function() { let called = 0; const childSchema = new Schema({ _id: false, value: { type: String, get: function(v) { ++called; return v.toUpperCase(); } } }, { toJSON: { getters: true } }); const schema = new Schema({ name: childSchema }); const Test = db.model('Test', schema); const doc = new Test({ name: { value: 'John Smith' } }); const res = doc.toJSON(); assert.equal(called, 1); assert.deepStrictEqual(res.name, { value: 'JOHN SMITH' }); }); it('setting doc array to array of top-level docs works (gh-5632)', function(done) { const MainSchema = new Schema({ name: { type: String }, children: [{ name: { type: String } }] }); const RelatedSchema = new Schema({ name: { type: String } }); const Model = db.model('Test', MainSchema); const RelatedModel = db.model('Test1', RelatedSchema); RelatedModel.create({ name: 'test' }, function(error, doc) { assert.ifError(error); Model.create({ name: 'test1', children: [doc] }, function(error, m) { assert.ifError(error); m.children = [doc]; m.save(function(error) { assert.ifError(error); assert.equal(m.children.length, 1); assert.equal(m.children[0].name, 'test'); done(); }); }); }); }); it('Using set as a schema path (gh-1939)', function(done) { const testSchema = new Schema({ set: String }); const Test = db.model('Test', testSchema); const t = new Test({ set: 'test 1' }); assert.equal(t.set, 'test 1'); t.save(function(error) { assert.ifError(error); t.set = 'test 2'; t.save(function(error) { assert.ifError(error); assert.equal(t.set, 'test 2'); done(); }); }); }); it('handles array defaults correctly (gh-5780)', function() { const testSchema = new Schema({ nestedArr: { type: [[Number]], default: [[0, 1]] } }); const Test = db.model('Test', testSchema); const t = new Test({}); assert.deepEqual(t.toObject().nestedArr, [[0, 1]]); t.nestedArr.push([1, 2]); const t2 = new Test({}); assert.deepEqual(t2.toObject().nestedArr, [[0, 1]]); }); it('sets path to the empty string on save after query (gh-6477)', async function() { const schema = new Schema({ name: String, s: { type: String, default: '' } }); const Test = db.model('Test', schema); const test = new Test(); assert.strictEqual(test.s, ''); // use native driver directly to insert an empty doc await Test.collection.insertOne({}); // udate the doc with the expectation that default booleans will be saved. const found = await Test.findOne({}); found.name = 'Max'; await found.save(); // use native driver directly to check doc for saved string const final = await Test.collection.findOne({}); assert.strictEqual(final.name, 'Max'); assert.strictEqual(final.s, ''); }); it('sets path to the default boolean on save after query (gh-6477)', async function() { const schema = new Schema({ name: String, f: { type: Boolean, default: false }, t: { type: Boolean, default: true } }); const Test = db.model('Test', schema); // use native driver directly to kill the fields await Test.collection.insertOne({}); // udate the doc with the expectation that default booleans will be saved. const found = await Test.findOne({}); found.name = 'Britney'; await found.save(); // use native driver directly to check doc for saved string const final = await Test.collection.findOne({}); assert.strictEqual(final.name, 'Britney'); assert.strictEqual(final.t, true); assert.strictEqual(final.f, false); }); it('virtuals with no getters return undefined (gh-6223)', function() { const personSchema = new mongoose.Schema({ name: { type: String }, children: [{ name: { type: String } }] }, { toObject: { getters: true, virtuals: true }, toJSON: { getters: true, virtuals: true }, id: false }); personSchema.virtual('favoriteChild').set(function(v) { return this.set('children.0', v); }); personSchema.virtual('heir').get(function() { return this.get('children.0'); }); const Person = db.model('Person', personSchema); const person = new Person({ name: 'Anakin' }); assert.strictEqual(person.favoriteChild, void 0); assert.ok(!('favoriteChild' in person.toJSON())); assert.ok(!('favoriteChild' in person.toObject())); }); it('add default getter/setter (gh-6262)', function() { const testSchema = new mongoose.Schema({}); testSchema.virtual('totalValue'); const Test = db.model('Test', testSchema); assert.equal(Test.schema.virtuals.totalValue.getters.length, 1); assert.equal(Test.schema.virtuals.totalValue.setters.length, 1); const doc = new Test(); doc.totalValue = 5; assert.equal(doc.totalValue, 5); }); it('calls array getters (gh-9889)', function() { let called = 0; const testSchema = new mongoose.Schema({ arr: [{ type: 'ObjectId', ref: 'Doesnt Matter', get: () => { ++called; return 42; } }] }); const Test = db.model('Test', testSchema); const doc = new Test({ arr: [new mongoose.Types.ObjectId()] }); assert.deepEqual(doc.toObject({ getters: true }).arr, [42]); assert.equal(called, 1); }); it('doesnt call setters when init-ing an array (gh-9889)', async function() { let called = 0; const testSchema = new mongoose.Schema({ arr: [{ type: 'ObjectId', set: v => { ++called; return v; } }] }); const Test = db.model('Test', testSchema);
let doc = await Test.create({ arr: [new mongoose.Types.ObjectId()] }); assert.equal(called, 1); called = 0; doc = await Test.findById(doc._id); assert.ok(doc); assert.equal(called, 0); }); it('nested virtuals + nested toJSON (gh-6294)', function() { const schema = mongoose.Schema({ nested: { prop: String } }, { _id: false, id: false }); schema.virtual('nested.virtual').get(() => 'test 2'); schema.set('toJSON', { virtuals: true }); const MyModel = db.model('Test', schema); const doc = new MyModel({ nested: { prop: 'test 1' } }); assert.deepEqual(doc.toJSON(), { nested: { prop: 'test 1', virtual: 'test 2' } }); assert.deepEqual(doc.nested.toJSON(), { prop: 'test 1', virtual: 'test 2' }); }); it('Disallows writing to __proto__ and other special properties', function() { const schema = new mongoose.Schema({ name: String }, { strict: false }); const Model = db.model('Test', schema); const doc = new Model({ '__proto__.x': 'foo' }); assert.strictEqual(Model.x, void 0); doc.set('__proto__.y', 'bar'); assert.strictEqual(Model.y, void 0); doc.set('constructor.prototype.z', 'baz'); assert.strictEqual(Model.z, void 0); }); it('save() depopulates pushed arrays (gh-6048)', async function() { const blogPostSchema = new Schema({ comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }] }); const BlogPost = db.model('BlogPost', blogPostSchema); const commentSchema = new Schema({ text: String }); const Comment = db.model('Comment', commentSchema);
let blogPost = await BlogPost.create({}); const comment = await Comment.create({ text: 'Hello' }); blogPost = await BlogPost.findById(blogPost); blogPost.comments.push(comment); await blogPost.save(); const savedBlogPost = await BlogPost.collection. findOne({ _id: blogPost._id }); assert.equal(savedBlogPost.comments.length, 1); assert.equal(savedBlogPost.comments[0].constructor.name, 'ObjectId'); assert.equal(savedBlogPost.comments[0].toString(), blogPost.comments[0]._id.toString()); }); it('Handles setting populated path set via `Document#populate()` (gh-7302)', function() { const authorSchema = new Schema({ name: String }); const bookSchema = new Schema({ author: { type: mongoose.Schema.Types.ObjectId, ref: 'Author' } }); const Author = db.model('Author', authorSchema); const Book = db.model('Book', bookSchema); return Author.create({ name: 'Victor Hugo' }). then(function(author) { return Book.create({ author: author._id }); }). then(function() { return Book.findOne(); }). then(function(doc) { return doc.populate('author'); }). then(function(doc) { doc.author = {}; assert.ok(!doc.author.name); assert.ifError(doc.validateSync()); }); }); it('Single nested subdocs using discriminator can be modified (gh-5693)', function(done) { const eventSchema = new Schema({ message: String }, { discriminatorKey: 'kind', _id: false }); const trackSchema = new Schema({ event: eventSchema }); trackSchema.path('event').discriminator('Clicked', new Schema({ element: String }, { _id: false })); trackSchema.path('event').discriminator('Purchased', new Schema({ product: String }, { _id: false })); const MyModel = db.model('Test', trackSchema); const doc = new MyModel({ event: { message: 'Test', kind: 'Clicked', element: 'Amazon Link' } }); doc.save(function(error) { assert.ifError(error); assert.equal(doc.event.message, 'Test'); assert.equal(doc.event.kind, 'Clicked'); assert.equal(doc.event.element, 'Amazon Link'); doc.set('event', { kind: 'Purchased', product: 'Professional AngularJS' }); doc.save(function(error) { assert.ifError(error); assert.equal(doc.event.kind, 'Purchased'); assert.equal(doc.event.product, 'Professional AngularJS'); assert.ok(!doc.event.element); assert.ok(!doc.event.message); done(); }); }); }); it('required function only gets called once (gh-6801)', function() { let reqCount = 0; const childSchema = new Schema({ name: { type: String, required: function() { reqCount++; return true; } } }); const Child = db.model('Child', childSchema); const parentSchema = new Schema({ name: String, child: childSchema }); const Parent = db.model('Parent', parentSchema); const child = new Child(/* name is required */); const parent = new Parent({ child: child }); return parent.validate().then( () => assert.ok(false), error => { assert.equal(reqCount, 1); assert.ok(error.errors['child.name']); } ); }); it('required function called again after save() (gh-6892)', async function() { const schema = new mongoose.Schema({ field: { type: String, default: null, required: function() { return this && this.field === undefined; } } }); const Model = db.model('Test', schema);
await Model.create({}); const doc1 = await Model.findOne({}).select({ _id: 1 }); await doc1.save(); // Should not throw await Model.create({}); }); it('doc array: set then remove (gh-3511)', function(done) { const ItemChildSchema = new mongoose.Schema({ name: { type: String, required: true } }); const ItemParentSchema = new mongoose.Schema({ children: [ItemChildSchema] }); const ItemParent = db.model('Parent', ItemParentSchema); const p = new ItemParent({ children: [{ name: 'test1' }, { name: 'test2' }] }); p.save(function(error) { assert.ifError(error); ItemParent.findById(p._id, function(error, doc) { assert.ifError(error); assert.ok(doc); assert.equal(doc.children.length, 2); doc.children[1].name = 'test3'; doc.children.remove(doc.children[0]); doc.save(function(error) { assert.ifError(error); ItemParent.findById(doc._id, function(error, doc) { assert.ifError(error); assert.equal(doc.children.length, 1); assert.equal(doc.children[0].name, 'test3'); done(); }); }); }); }); }); it('doc array: modify then sort (gh-7556)', async function() { const assetSchema = new Schema({ name: { type: String, required: true }, namePlural: { type: String, required: true } }); assetSchema.pre('validate', function() { if (this.isNew) { this.namePlural = this.name + 's'; } }); const personSchema = new Schema({ name: String, assets: [assetSchema] }); const Person = db.model('Person', personSchema); await Person.create({ name: 'test', assets: [{ name: 'Cash', namePlural: 'Cash' }] }); const p = await Person.findOne(); p.assets.push({ name: 'Home' }); p.assets.id(p.assets[0].id).set('name', 'Cash'); p.assets.id(p.assets[0].id).set('namePlural', 'Cash'); p.assets.sort((doc1, doc2) => doc1.name > doc2.name ? -1 : 1); await p.save(); }); it('modifying unselected nested object (gh-5800)', function() { const MainSchema = new mongoose.Schema({ a: { b: { type: String, default: 'some default' }, c: { type: Number, default: 0 }, d: { type: String } }, e: { type: String } }); MainSchema.pre('save', function(next) { if (this.isModified()) { this.set('a.c', 100, Number); } next(); }); const Main = db.model('Test', MainSchema); const doc = { a: { b: 'not the default', d: 'some value' }, e: 'e' }; return Main.create(doc). then(function(doc) { assert.equal(doc.a.b, 'not the default'); assert.equal(doc.a.d, 'some value'); return Main.findOne().select('e'); }). then(function(doc) { doc.e = 'e modified'; return doc.save(); }). then(function() { return Main.findOne(); }). then(function(doc) { assert.equal(doc.a.b, 'not the default'); assert.equal(doc.a.d, 'some value'); }); }); it('set() underneath embedded discriminator (gh-6482)', async function() { const mediaSchema = new Schema({ file: String }, { discriminatorKey: 'kind', _id: false }); const photoSchema = new Schema({ position: String }); const pageSchema = new Schema({ media: mediaSchema }); pageSchema.path('media').discriminator('photo', photoSchema); const Page = db.model('Test', pageSchema);
let doc = await Page.create({ media: { kind: 'photo', file: 'cover.jpg', position: 'left' } }); // Using positional args syntax doc.set('media.position', 'right'); assert.equal(doc.media.position, 'right'); await doc.save(); doc = await Page.findById(doc._id); assert.equal(doc.media.position, 'right'); // Using object syntax doc.set({ 'media.position': 'left' }); assert.equal(doc.media.position, 'left'); await doc.save(); doc = await Page.findById(doc._id); assert.equal(doc.media.position, 'left'); }); it('set() underneath array embedded discriminator (gh-6526)', async function() { const mediaSchema = new Schema({ file: String }, { discriminatorKey: 'kind', _id: false }); const photoSchema = new Schema({ position: String }); const pageSchema = new Schema({ media: [mediaSchema] }); pageSchema.path('media').discriminator('photo', photoSchema); const Page = db.model('Test', pageSchema);
let doc = await Page.create({ media: [{ kind: 'photo', file: 'cover.jpg', position: 'left' }] }); // Using positional args syntax doc.set('media.0.position', 'right'); assert.equal(doc.media[0].position, 'right'); await doc.save(); doc = await Page.findById(doc._id); assert.equal(doc.media[0].position, 'right'); }); it('consistent context for nested docs (gh-5347)', function(done) { const contexts = []; const childSchema = new mongoose.Schema({ phoneNumber: { type: String, required: function() { contexts.push(this); return this.notifications.isEnabled; } }, notifications: { isEnabled: { type: Boolean, required: true } } }); const parentSchema = new mongoose.Schema({ name: String, children: [childSchema] }); const Parent = db.model('Parent', parentSchema); Parent.create({ name: 'test', children: [ { phoneNumber: '123', notifications: { isEnabled: true } } ] }, function(error, doc) { assert.ifError(error); const child = doc.children.id(doc.children[0]._id); child.phoneNumber = '345'; assert.equal(contexts.length, 1); doc.save(function(error) { assert.ifError(error); assert.equal(contexts.length, 2); assert.ok(contexts[0].toObject().notifications.isEnabled); assert.ok(contexts[1].toObject().notifications.isEnabled); done(); }); }); }); it('accessing arrays in setters on initial document creation (gh-6155)', function() { const artistSchema = new mongoose.Schema({ name: { type: String, set: function(v) { const splitStrings = v.split(' '); for (const keyword of splitStrings) { this.keywords.push(keyword); } return v; } }, keywords: [String] }); const Artist = db.model('Test', artistSchema); const artist = new Artist({ name: 'Motley Crue' }); assert.deepEqual(artist.toObject().keywords, ['Motley', 'Crue']); }); it('handles 2nd level nested field with null child (gh-6187)', function() { const NestedSchema = new Schema({ parent: new Schema({ name: String, child: { name: String } }, { strict: false }) }); const NestedModel = db.model('Test', NestedSchema); const n = new NestedModel({ parent: { name: 'foo', child: null // does not fail if undefined } }); assert.equal(n.parent.name, 'foo'); }); it('does not call default function on init if value set (gh-6410)', async function() { let called = 0; function generateRandomID() { called++; return called; } const TestDefaultsWithFunction = db.model('Test', new Schema({ randomID: { type: Number, default: generateRandomID } })); const post = new TestDefaultsWithFunction(); assert.equal(post.get('randomID'), 1); assert.equal(called, 1);
await post.save(); await TestDefaultsWithFunction.findById(post._id); assert.equal(called, 1); }); describe('convertToFalse and convertToTrue (gh-6758)', function() { let convertToFalse = null; let convertToTrue = null; beforeEach(function() { convertToFalse = new Set(mongoose.Schema.Types.Boolean.convertToFalse); convertToTrue = new Set(mongoose.Schema.Types.Boolean.convertToTrue); }); afterEach(function() { mongoose.Schema.Types.Boolean.convertToFalse = convertToFalse; mongoose.Schema.Types.Boolean.convertToTrue = convertToTrue; }); it('lets you add custom strings that get converted to true/false', function() { const TestSchema = new Schema({ b: Boolean }); const Test = db.model('Test', TestSchema); mongoose.Schema.Types.Boolean.convertToTrue.add('aye'); mongoose.Schema.Types.Boolean.convertToFalse.add('nay'); const doc1 = new Test({ b: 'aye' }); const doc2 = new Test({ b: 'nay' }); assert.strictEqual(doc1.b, true); assert.strictEqual(doc2.b, false); return doc1.save(). then(() => Test.findOne({ b: { $exists: 'aye' } })). then(doc => assert.ok(doc)). then(() => { mongoose.Schema.Types.Boolean.convertToTrue.delete('aye'); mongoose.Schema.Types.Boolean.convertToFalse.delete('nay'); }); }); it('allows adding `null` to list of values that convert to false (gh-9223)', function() { const TestSchema = new Schema({ b: Boolean }); const Test = db.model('Test', TestSchema); mongoose.Schema.Types.Boolean.convertToFalse.add(null); const doc1 = new Test({ b: null }); const doc2 = new Test(); doc2.init({ b: null }); assert.strictEqual(doc1.b, false); assert.strictEqual(doc2.b, false); }); }); it('doesnt double-call getters when using get() (gh-6779)', function() { const schema = new Schema({ nested: { arr: [{ key: String }] } }); schema.path('nested.arr.0.key').get(v => { return 'foobar' + v; }); const M = db.model('Test', schema); const test = new M(); test.nested.arr.push({ key: 'value' }); test.nested.arr.push({ key: 'value2' }); assert.equal(test.get('nested.arr.0.key'), 'foobarvalue'); assert.equal(test.get('nested.arr.1.key'), 'foobarvalue2'); return Promise.resolve(); }); it('returns doubly nested field in inline sub schema when using get() (gh-6925)', function() { const child = new Schema({ nested: { key: String } }); const parent = new Schema({ child: child }); const M = db.model('Test', parent); const test = new M({ child: { nested: { key: 'foobarvalue' } } }); assert.equal(test.get('child.nested.key'), 'foobarvalue'); return Promise.resolve(); }); it('defaults should see correct isNew (gh-3793)', async function() { let isNew = []; const TestSchema = new mongoose.Schema({ test: { type: Date, default: function() { isNew.push(this.isNew); if (this.isNew) { return Date.now(); } return void 0; } } }); const TestModel = db.model('Test', TestSchema);
await Promise.resolve(db); await TestModel.collection.insertOne({}); let doc = await TestModel.findOne({}); assert.strictEqual(doc.test, void 0); assert.deepEqual(isNew, [false]); isNew = []; doc = await TestModel.create({}); assert.ok(doc.test instanceof Date); assert.deepEqual(isNew, [true]); }); it('modify multiple subdoc paths (gh-4405)', function(done) { const ChildObjectSchema = new Schema({ childProperty1: String, childProperty2: String, childProperty3: String }); const ParentObjectSchema = new Schema({ parentProperty1: String, parentProperty2: String, child: ChildObjectSchema }); const Parent = db.model('Parent', ParentObjectSchema); const p = new Parent({ parentProperty1: 'abc', parentProperty2: '123', child: { childProperty1: 'a', childProperty2: 'b', childProperty3: 'c' } }); p.save(function(error) { assert.ifError(error); Parent.findById(p._id, function(error, p) { assert.ifError(error); p.parentProperty1 = 'foo'; p.parentProperty2 = 'bar'; p.child.childProperty1 = 'ping'; p.child.childProperty2 = 'pong'; p.child.childProperty3 = 'weee'; p.save(function(error) { assert.ifError(error); Parent.findById(p._id, function(error, p) { assert.ifError(error); assert.equal(p.child.childProperty1, 'ping'); assert.equal(p.child.childProperty2, 'pong'); assert.equal(p.child.childProperty3, 'weee'); done(); }); }); }); }); }); it('doesnt try to cast populated embedded docs (gh-6390)', async function() { const otherSchema = new Schema({ name: String }); const subSchema = new Schema({ my: String, other: { type: Schema.Types.ObjectId, refPath: 'sub.my' } }); const schema = new Schema({ name: String, sub: subSchema }); const Other = db.model('Test1', otherSchema); const Test = db.model('Test', schema); const other = new Other({ name: 'Nicole' }); const test = new Test({ name: 'abc', sub: { my: 'Test1', other: other._id } }); await other.save(); await test.save(); const doc = await Test.findOne({}).populate('sub.other'); assert.strictEqual('Nicole', doc.sub.other.name); }); }); describe('clobbered Array.prototype', function() { beforeEach(() => db.deleteModel(/.*/)); afterEach(function() { delete Array.prototype.remove; }); it('handles clobbered Array.prototype.remove (gh-6431)', function() { Object.defineProperty(Array.prototype, 'remove', { value: 42, configurable: true, writable: false }); const schema = new Schema({ arr: [{ name: String }] }); const MyModel = db.model('Test', schema); const doc = new MyModel(); assert.deepEqual(doc.toObject().arr, []); }); it('calls array validators again after save (gh-6818)', async function() { const schema = new Schema({ roles: { type: [{ name: String, folders: { type: [{ folderId: String }], validate: v => assert.ok(v.length === new Set(v.map(el => el.folderId)).size, 'Duplicate') } }] } }); const Model = db.model('Test', schema); await Model.create({ roles: [ { name: 'admin' }, { name: 'mod', folders: [{ folderId: 'foo' }] } ] }); const doc = await Model.findOne(); doc.roles[1].folders.push({ folderId: 'bar' }); await doc.save(); doc.roles[1].folders[1].folderId = 'foo'; let threw = false; try { await doc.save(); } catch (error) { threw = true; assert.equal(error.errors['roles.1.folders'].reason.message, 'Duplicate'); } assert.ok(threw); }); it('set single nested to num throws ObjectExpectedError (gh-6710) (gh-6753)', function() { const schema = new Schema({ nested: new Schema({ num: Number }) }); const Test = db.model('Test', schema); const doc = new Test({ nested: { num: 123 } }); doc.nested = 123; return doc.validate(). then(() => { throw new Error('Should have errored'); }). catch(err => { assert.ok(err.message.indexOf('Cast to Embedded') !== -1, err.message); assert.equal(err.errors['nested'].reason.name, 'ObjectExpectedError'); const doc = new Test({ nested: { num: 123 } }); doc.nested = []; return doc.validate(); }). then(() => { throw new Error('Should have errored'); }). catch(err => { assert.ok(err.message.indexOf('Cast to Embedded') !== -1, err.message); assert.equal(err.errors['nested'].reason.name, 'ObjectExpectedError'); }); }); it('set array to false throws ObjectExpectedError (gh-7242)', function() { const Child = new mongoose.Schema({}); const Parent = new mongoose.Schema({ children: [Child] }); const ParentModel = db.model('Parent', Parent); const doc = new ParentModel({ children: false }); return doc.save().then( () => assert.ok(false), err => { assert.ok(err.errors['children']); assert.equal(err.errors['children'].name, 'ObjectParameterError'); } ); }); }); it('does not save duplicate items after two saves (gh-6900)', async function() { const M = db.model('Test', { items: [{ name: String }] }); const doc = new M(); doc.items.push({ name: '1' });
await doc.save(); doc.items.push({ name: '2' }); await doc.save(); const found = await M.findById(doc.id); assert.equal(found.items.length, 2); }); it('validateSync() on embedded doc (gh-6931)', async function() { const innerSchema = new mongoose.Schema({ innerField: { type: mongoose.Schema.Types.ObjectId, required: true } }); const schema = new mongoose.Schema({ field: { type: mongoose.Schema.Types.ObjectId, required: true }, inner: [innerSchema] }); const Model = db.model('Test', schema);
const doc2 = new Model(); doc2.field = new mongoose.Types.ObjectId(); doc2.inner.push({ innerField: new mongoose.Types.ObjectId() }); doc2.inner[0].innerField = ''; let err = doc2.inner[0].validateSync(); assert.ok(err); assert.ok(err.errors['innerField']); err = await doc2.inner[0].validate().then(() => assert.ok(false), err => err); assert.ok(err); assert.ok(err.errors['innerField']); }); it('retains user-defined key order with nested docs (gh-6944)', function() { const schema = new Schema({ _id: String, foo: String, bar: { a: String } }); const Model = db.model('Test', schema); const doc = new Model({ _id: 'test', foo: 'hello', bar: { a: 'world' } }); // Same order as in the initial set above assert.deepEqual(Object.keys(doc._doc), ['_id', 'foo', 'bar']); return Promise.resolve(); }); it('does not mark modified if setting nested subdoc to same value (gh-7048)', async function() { const BarSchema = new Schema({ bar: String }, { _id: false }); const FooNestedSchema = new Schema({ foo: BarSchema }); const Model = db.model('Test', FooNestedSchema);
const doc = await Model.create({ foo: { bar: 'test' } }); doc.set({ foo: { bar: 'test' } }); assert.deepEqual(doc.modifiedPaths(), []); doc.set('foo.bar', 'test'); assert.deepEqual(doc.modifiedPaths(), []); }); it('allow saving validation error in db (gh-7127)', async function() { const schema = new Schema({ error: mongoose.Schema.Types.Mixed, name: { type: String, required: true } }); const Model = db.model('Test', schema); const doc = new Model(); const error = await doc.validate().catch(error => error); doc.name = 'foo'; doc.error = error; await doc.save(); const fromDb = await Model.findOne(); assert.ok(fromDb.error.errors.name); }); it('handles mixed arrays with all syntaxes (gh-7109)', function() { const schema = new Schema({ arr1: [Schema.Types.Mixed], arr2: [{}], arr3: [Object] }); const Test = db.model('Test', schema); const test = new Test({ arr1: ['test1', { two: 'three' }, [4, 'five', 6]], arr2: ['test2', { three: 'four' }, [5, 'six', 7]], arr3: ['test3', { four: 'five' }, [6, 'seven', 8]] }); assert.ok(test.validateSync() == null, test.validateSync()); return Promise.resolve(); }); it('propsParameter option (gh-7145)', async function() { const schema = new Schema({ name: { type: String, validate: { validator: (v, props) => props.validator != null, propsParameter: true } } }); const Test = db.model('Test', schema); const doc = new Test({ name: 'foo' }); const syncValidationError = doc.validateSync(); assert.ok(syncValidationError == null, syncValidationError);
const asyncValidationError = await doc.validate().then(() => null, err => err); assert.ok(asyncValidationError == null, asyncValidationError); }); it('surfaces errors in subdoc pre validate (gh-7187)', function() { const InnerSchema = new Schema({ name: String }); InnerSchema.pre('validate', function() { throw new Error('Oops!'); }); const TestSchema = new Schema({ subdocs: [InnerSchema] }); const Test = db.model('Test', TestSchema); return Test.create({ subdocs: [{ name: 'foo' }] }).then( () => { throw new Error('Fail'); }, err => { assert.ok(err.message.indexOf('Oops!') !== -1, err.message); } ); }); it('runs setter only once when doing .set() underneath single nested (gh-7196)', function() { let called = []; const InnerSchema = new Schema({ name: String, withSetter: { type: String, set: function(v) { called.push(this); return v; } } }); const TestSchema = new Schema({ nested: InnerSchema }); const Model = db.model('Test', TestSchema); const doc = new Model({ nested: { name: 'foo' } }); // Make sure setter only gets called once called = []; doc.set('nested.withSetter', 'bar'); assert.equal(called.length, 1); assert.equal(called[0].name, 'foo'); return Promise.resolve(); }); it('should enable key with dot(.) on mixed types with checkKeys (gh-7144)', async function() { const s = new Schema({ raw: { type: Schema.Types.Mixed } }); const M = db.model('Test', s); const raw = { 'foo.bar': 'baz' };
let doc = await M.create([{ raw: raw }], { checkKeys: false }). then(res => res[0]); assert.deepEqual(doc.raw, raw); doc = await M.findOneAndUpdate({}, { raw: { 'a.b': 2 } }, { new: true }); assert.deepEqual(doc.raw, { 'a.b': 2 }); }); it('doesnt mark array as modified on init if embedded schema has default (gh-7227)', async function() { const subSchema = new mongoose.Schema({ users: { type: [{ name: { type: String } }], // This test ensures the whole array won't be modified on init because // of this default default: [{ name: 'test' }] } }); const schema = new mongoose.Schema({ sub: [subSchema] }); const Model = db.model('Test', schema);
let doc = new Model({ name: 'test', sub: [{}] }); await doc.save(); assert.ok(!doc.isModified()); doc = await Model.findOne(); assert.ok(!doc.isModified()); }); it('casts defaults for doc arrays (gh-7337)', async function() { const accountSchema = new mongoose.Schema({ roles: { type: [{ otherProperties: { example: Boolean }, name: String }], default: function() { return [ { otherProperties: { example: true }, name: 'First' }, { otherProperties: { example: false }, name: 'Second' } ]; } } }); const Account = db.model('Test', accountSchema);
await Account.create({}); const doc = await Account.findOne(); assert.ok(doc.roles[0]._id); assert.ok(doc.roles[1]._id); }); it('updateOne() hooks (gh-7133) (gh-7423)', async function() { const schema = new mongoose.Schema({ name: String }); let queryCount = 0; let docCount = 0; let docPostCount = 0; let docRegexCount = 0; let docPostRegexCount = 0; schema.pre('updateOne', () => ++queryCount); schema.pre('updateOne', { document: true, query: false }, () => ++docCount); schema.post('updateOne', { document: true, query: false }, () => ++docPostCount); schema.pre(/^updateOne$/, { document: true, query: false }, () => ++docRegexCount); schema.post(/^updateOne$/, { document: true, query: false }, () => ++docPostRegexCount); let removeCount1 = 0; let removeCount2 = 0; schema.pre('remove', () => ++removeCount1); schema.pre('remove', { document: true, query: false }, () => ++removeCount2); const Model = db.model('Test', schema);
const doc = new Model({ name: 'test' }); await doc.save(); assert.equal(queryCount, 0); assert.equal(docCount, 0); assert.equal(docPostCount, 0); assert.equal(docRegexCount, 0); assert.equal(docPostRegexCount, 0); await doc.updateOne({ name: 'test2' }); assert.equal(queryCount, 1); assert.equal(docCount, 1); assert.equal(docPostCount, 1); assert.equal(docRegexCount, 1); assert.equal(docPostRegexCount, 1); assert.equal(removeCount1, 0); assert.equal(removeCount2, 0); await doc.remove(); assert.equal(removeCount1, 1); assert.equal(removeCount2, 1); }); it('doesnt mark single nested doc date as modified if setting with string (gh-7264)', async function() { const subSchema = new mongoose.Schema({ date2: Date }); const schema = new mongoose.Schema({ date1: Date, sub: subSchema }); const Model = db.model('Test', schema);
const date = '2018-11-22T09:00:00.000Z'; const doc = await Model.create({ date1: date, sub: { date2: date } }); assert.deepEqual(doc.modifiedPaths(), []); doc.set('date1', date); doc.set('sub.date2', date); assert.deepEqual(doc.modifiedPaths(), []); }); it('handles null `fields` param to constructor (gh-7271)', function() { const ActivityBareSchema = new Schema({ _id: { type: Schema.Types.ObjectId, ref: 'Activity' }, name: String }); const EventSchema = new Schema({ activity: ActivityBareSchema, name: String }); const data = { name: 'Test', activity: { _id: '5bf606f6471b6056b3f2bfc9', name: 'Activity name' } }; const Event = db.model('Test', EventSchema); const event = new Event(data, null); assert.equal(event.activity.name, 'Activity name'); return event.validate(); }); it('flattenMaps option for toObject() (gh-10872) (gh-7274) (gh-10486)', function() { const subSchema = new Schema({ name: String }); let schema = new Schema({ test: { type: Map, of: subSchema, default: new Map() } }, { versionKey: false }); let Test = db.model('Test', schema); let mapTest = new Test({}); mapTest.test.set('key1', { name: 'value1' }); // getters: true for gh-10486 assert.equal(mapTest.toObject({ getters: true, flattenMaps: true }).test.key1.name, 'value1'); assert.equal(mapTest.toJSON({ getters: true, flattenMaps: true }).test.key1.name, 'value1'); assert.equal(mapTest.toJSON({ getters: true, flattenMaps: false }).test.get('key1').name, 'value1'); schema = new Schema({ test: { type: Map, of: subSchema, default: new Map() } }, { versionKey: false }); schema.set('toObject', { flattenMaps: true }); db.deleteModel('Test'); Test = db.model('Test', schema); mapTest = new Test({}); mapTest.test.set('key1', { name: 'value1' }); assert.equal(mapTest.toObject({}).test.key1.name, 'value1'); }); it('`collection` property with strict: false (gh-7276)', async function() { const schema = new Schema({}, { strict: false, versionKey: false }); const Model = db.model('Test', schema); let doc = new Model({ test: 'foo', collection: 'bar' }); await doc.save(); assert.equal(doc.collection, 'bar'); doc = await Model.findOne(); assert.equal(doc.toObject().collection, 'bar'); }); it('should validateSync() all elements in doc array (gh-6746)', function() { const Model = db.model('Test', new Schema({ colors: [{ name: { type: String, required: true }, hex: { type: String, required: true } }] })); const model = new Model({ colors: [ { name: 'steelblue' }, { hex: '#4682B4' } ] }); const errors = model.validateSync().errors; const keys = Object.keys(errors).sort(); assert.deepEqual(keys, ['colors.0.hex', 'colors.1.name']); }); it('handles fake constructor (gh-7290)', async function() { const TestSchema = new Schema({ test: String }); const TestModel = db.model('Test', TestSchema); const badQuery = { test: { length: 1e10, constructor: { name: 'Array' } } };
let err = await TestModel.findOne(badQuery).then(() => null, e => e); assert.equal(err.name, 'CastError', err.stack); err = await TestModel.updateOne(badQuery, { name: 'foo' }). then(() => null, err => err); assert.equal(err.name, 'CastError', err.stack); err = await TestModel.updateOne({}, badQuery).then(() => null, e => e); assert.equal(err.name, 'CastError', err.stack); err = await TestModel.deleteOne(badQuery).then(() => null, e => e); assert.equal(err.name, 'CastError', err.stack); }); it('handles fake __proto__ (gh-7290)', async function() { const TestSchema = new Schema({ test: String, name: String }); const TestModel = db.model('Test', TestSchema); const badQuery = JSON.parse('{"test":{"length":1000000000,"__proto__":[]}}');
let err = await TestModel.findOne(badQuery).then(() => null, e => e); assert.equal(err.name, 'CastError', err.stack); err = await TestModel.updateOne(badQuery, { name: 'foo' }). then(() => null, err => err); assert.equal(err.name, 'CastError', err.stack); err = await TestModel.updateOne({}, badQuery).then(() => null, e => e); assert.equal(err.name, 'CastError', err.stack); err = await TestModel.deleteOne(badQuery).then(() => null, e => e); assert.equal(err.name, 'CastError', err.stack); }); it('cast error with string path set to array in db (gh-7619)', async function() { const TestSchema = new Schema({ name: String }); const TestModel = db.model('Test', TestSchema);
await TestModel.findOne(); await TestModel.collection.insertOne({ name: ['foo', 'bar'] }); const doc = await TestModel.findOne(); assert.ok(!doc.name); const err = doc.validateSync(); assert.ok(err); assert.ok(err.errors['name']); }); it('doesnt crash if nested path with `get()` (gh-7316)', function() { const schema = new mongoose.Schema({ http: { get: Number } }); const Model = db.model('Test', schema); return Model.create({ http: { get: 400 } }); // Should succeed }); it('copies atomics from existing document array when setting doc array (gh-7472)', async function() { const Dog = db.model('Test', new mongoose.Schema({ name: String, toys: [{ name: String }] }));
const dog = new Dog({ name: 'Dash' }); dog.toys.push({ name: '1' }); dog.toys.push({ name: '2' }); dog.toys.push({ name: '3' }); await dog.save(); for (const toy of ['4', '5', '6']) { dog.toys = dog.toys || []; dog.toys.push({ name: toy, count: 1 }); } await dog.save(); const fromDb = await Dog.findOne(); assert.deepEqual(fromDb.toys.map(t => t.name), ['1', '2', '3', '4', '5', '6']); }); it('doesnt fail with custom update function (gh-7342)', async function() { const catalogSchema = new mongoose.Schema({ name: String, sub: new Schema({ name: String }) }, { runSettersOnQuery: true }); catalogSchema.methods.update = function(data) { for (const key in data) { this[key] = data[key]; } return this.save(); }; const Catalog = db.model('Test', catalogSchema);
let doc = await Catalog.create({ name: 'test', sub: { name: 'foo' } }); doc = await doc.update({ name: 'test2' }); assert.equal(doc.name, 'test2'); }); it('setters that modify `this` should work on single nested when overwriting (gh-7585)', function() { const NameSchema = new Schema({ full: { type: String, set: function(v) { this.first = 'foo'; this.last = 'bar'; return v + ' baz'; } }, first: String, last: String }, { _id: false }); const User = db.model('User', new Schema({ name: { type: NameSchema, default: {} } })); const s = new User(); s.name = { full: 'test' }; assert.equal(s.name.first, 'foo'); assert.equal(s.name.last, 'bar'); assert.equal(s.name.full, 'test baz'); return Promise.resolve(); }); it('handles setting embedded doc to Object.assign() from another doc (gh-7645)', function() { const profileSchema = new Schema({ name: String, email: String }); const companyUserSchema = new Schema({ profile: { type: profileSchema, default: {} } }); const CompanyUser = db.model('User', companyUserSchema); const cu = new CompanyUser({ profile: { name: 'foo', email: 'bar' } }); cu.profile = Object.assign({}, cu.profile); assert.equal(cu.profile.name, 'foo'); assert.equal(cu.profile.email, 'bar'); assert.doesNotThrow(function() { cu.toObject(); }); }); it('setting single nested subdoc with custom date types and getters/setters (gh-7601)', async function() { const moment = require('moment'); const schema = new Schema({ start: { type: Date, get: get, set: set, required: true }, end: { type: Date, get: get, set: set, required: true } }, { toObject: { getters: true } }); function get(v) { return moment(v); } function set(v) { return v.toDate(); } const parentSchema = new Schema({ nested: schema }); const Model = db.model('Parent', parentSchema);
const doc = await Model.create({ nested: { start: moment('2019-01-01'), end: moment('2019-01-02') } }); doc.nested = { start: moment('2019-03-01'), end: moment('2019-04-01') }; await doc.save(); const _doc = await Model.collection.findOne(); assert.ok(_doc.nested.start instanceof Date); assert.ok(_doc.nested.end instanceof Date); }); it('get() and set() underneath alias (gh-7592)', async function() { const photoSchema = new Schema({ foo: String }); const pageSchema = new Schema({ p: { type: [photoSchema], alias: 'photos' } }); const Page = db.model('Test', pageSchema);
const doc = await Page.create({ p: [{ foo: 'test' }] }); assert.equal(doc.p[0].foo, 'test'); assert.equal(doc.get('photos.0.foo'), 'test'); doc.set('photos.0.foo', 'bar'); assert.equal(doc.p[0].foo, 'bar'); assert.equal(doc.get('photos.0.foo'), 'bar'); }); it('get() with getters: false (gh-7233)', function() { const testSchema = new Schema({ foo: { type: String, get: v => v.toLowerCase() } }); const Test = db.model('Test', testSchema); const doc = new Test({ foo: 'Bar' }); assert.equal(doc.foo, 'bar'); assert.equal(doc._doc.foo, 'Bar'); assert.equal(doc.get('foo'), 'bar'); assert.equal(doc.get('foo', null, { getters: false }), 'Bar'); return Promise.resolve(); }); it('overwriting single nested (gh-7660)', function() { const childSchema = new mongoose.Schema({ foo: String, bar: Number }, { _id: false, id: false }); const parentSchema = new mongoose.Schema({ child: childSchema }); const Test = db.model('Test', parentSchema); const test = new Test({ child: { foo: 'test', bar: 42 } }); test.set({ child: { foo: 'modified', bar: 43 } }); assert.deepEqual(test.toObject().child, { foo: 'modified', bar: 43 }); return Promise.resolve(); }); it('setting path to non-POJO object (gh-7639)', function() { class Nested { constructor(prop) { this.prop = prop; } } const schema = new Schema({ nested: { prop: String } }); const Model = db.model('Test', schema); const doc = new Model({ nested: { prop: '1' } }); doc.set('nested', new Nested('2')); assert.equal(doc.nested.prop, '2'); doc.set({ nested: new Nested('3') }); assert.equal(doc.nested.prop, '3'); }); it('supports setting date properties with strict: false (gh-7907)', function() { const schema = Schema({}, { strict: false }); const SettingsModel = db.model('Test', schema); const date = new Date(); const obj = new SettingsModel({ timestamp: date, subDoc: { timestamp: date } }); assert.strictEqual(obj.timestamp, date); assert.strictEqual(obj.subDoc.timestamp, date); }); it('handles .set() on doc array within embedded discriminator (gh-7656)', function() { const pageElementSchema = new Schema({ type: { type: String, required: true } }, { discriminatorKey: 'type' }); const textElementSchema = new Schema({ body: { type: String } }); const blockElementSchema = new Schema({ elements: [pageElementSchema] }); blockElementSchema.path('elements').discriminator('block', blockElementSchema); blockElementSchema.path('elements').discriminator('text', textElementSchema); const pageSchema = new Schema({ elements: [pageElementSchema] }); pageSchema.path('elements').discriminator('block', blockElementSchema); pageSchema.path('elements').discriminator('text', textElementSchema); const Page = db.model('Test', pageSchema); const page = new Page({ elements: [ { type: 'text', body: 'Page Title' }, { type: 'block', elements: [{ type: 'text', body: 'Page Content' }] } ] }); page.set('elements.0.body', 'Page Heading'); assert.equal(page.elements[0].body, 'Page Heading'); assert.equal(page.get('elements.0.body'), 'Page Heading'); page.set('elements.1.elements.0.body', 'Page Body'); assert.equal(page.elements[1].elements[0].body, 'Page Body'); assert.equal(page.get('elements.1.elements.0.body'), 'Page Body'); page.elements[1].elements[0].body = 'Page Body'; assert.equal(page.elements[1].elements[0].body, 'Page Body'); assert.equal(page.get('elements.1.elements.0.body'), 'Page Body'); }); it('$isEmpty() (gh-5369)', function() { const schema = new Schema({ nested: { foo: String }, subdoc: new Schema({ bar: String }, { _id: false }), docArr: [new Schema({ baz: String }, { _id: false })], mixed: {} }); const Model = db.model('Test', schema); const doc = new Model({ subdoc: {}, docArr: [{}] }); assert.ok(doc.nested.$isEmpty()); assert.ok(doc.subdoc.$isEmpty()); assert.ok(doc.docArr[0].$isEmpty()); assert.ok(doc.$isEmpty('nested')); assert.ok(doc.$isEmpty('subdoc')); assert.ok(doc.$isEmpty('docArr.0')); assert.ok(doc.$isEmpty('mixed')); doc.nested.foo = 'test'; assert.ok(!doc.nested.$isEmpty()); assert.ok(doc.subdoc.$isEmpty()); assert.ok(doc.docArr[0].$isEmpty()); assert.ok(!doc.$isEmpty('nested')); assert.ok(doc.$isEmpty('subdoc')); assert.ok(doc.$isEmpty('docArr.0')); assert.ok(doc.$isEmpty('mixed')); doc.subdoc.bar = 'test'; assert.ok(!doc.nested.$isEmpty()); assert.ok(!doc.subdoc.$isEmpty()); assert.ok(doc.docArr[0].$isEmpty()); assert.ok(!doc.$isEmpty('nested')); assert.ok(!doc.$isEmpty('subdoc')); assert.ok(doc.$isEmpty('docArr.0')); assert.ok(doc.$isEmpty('mixed')); doc.docArr[0].baz = 'test'; assert.ok(!doc.nested.$isEmpty()); assert.ok(!doc.subdoc.$isEmpty()); assert.ok(!doc.docArr[0].$isEmpty()); assert.ok(!doc.$isEmpty('nested')); assert.ok(!doc.$isEmpty('subdoc')); assert.ok(!doc.$isEmpty('docArr.0')); assert.ok(doc.$isEmpty('mixed')); doc.mixed = {}; assert.ok(doc.$isEmpty('mixed')); doc.mixed.test = 1; assert.ok(!doc.$isEmpty('mixed')); return Promise.resolve(); }); it('push() onto discriminator doc array (gh-7704)', function() { const opts = { minimize: false, // So empty objects are returned strict: true, typeKey: '$type', // So that we can use fields named `type` discriminatorKey: 'type' }; const IssueSchema = new mongoose.Schema({ _id: String, text: String, type: String }, opts); const IssueModel = db.model('Test', IssueSchema); const SubIssueSchema = new mongoose.Schema({ checklist: [{ completed: { $type: Boolean, default: false } }] }, opts); IssueModel.discriminator('gh7704_sub', SubIssueSchema); const doc = new IssueModel({ _id: 'foo', text: 'text', type: 'gh7704_sub' }); doc.checklist.push({ completed: true }); assert.ifError(doc.validateSync()); return Promise.resolve(); }); it('doesnt call getter when saving (gh-7719)', function() { let called = 0; const kittySchema = new mongoose.Schema({ name: { type: String, get: function(v) { ++called; return v; } } }); const Kitten = db.model('Test', kittySchema); const k = new Kitten({ name: 'Mr Sprinkles' }); return k.save().then(() => assert.equal(called, 0)); }); it('skips malformed validators property (gh-7720)', function() { const NewSchema = new Schema({ object: { type: 'string', validators: ['string'] // This caused the issue } }); const TestModel = db.model('Test', NewSchema); const instance = new TestModel(); instance.object = 'value'; assert.ifError(instance.validateSync()); return instance.validate(); }); it('nested set on subdocs works (gh-7748)', async function() { const geojsonSchema = new Schema({ type: { type: String, default: 'Feature' }, geometry: { type: { type: String, required: true }, coordinates: { type: [] } }, properties: { type: Object } }); const userSchema = new Schema({ position: geojsonSchema }); const User = db.model('User', userSchema); const position = { geometry: { type: 'Point', coordinates: [1.11111, 2.22222] }, properties: { a: 'b' } }; const newUser = new User({ position: position }); await newUser.save(); const editUser = await User.findById(newUser._id); editUser.position = position; await editUser.validate(); await editUser.save(); const fromDb = await User.findById(newUser._id); assert.equal(fromDb.position.properties.a, 'b'); assert.equal(fromDb.position.geometry.coordinates[0], 1.11111); }); it('does not convert array to object with strict: false (gh-7733)', async function() { const ProductSchema = new mongoose.Schema({}, { strict: false }); const Product = db.model('Test', ProductSchema);
await Product.create({ arr: [{ test: 1 }, { test: 2 }] }); const doc = await Product.collection.findOne(); assert.ok(Array.isArray(doc.arr)); assert.deepEqual(doc.arr, [{ test: 1 }, { test: 2 }]); }); it('does not crash with array property named "undefined" (gh-7756)', async function() { const schema = new Schema({ undefined: [String] }); const Model = db.model('Test', schema);
const doc = await Model.create({ undefined: ['foo'] }); doc['undefined'].push('bar'); await doc.save(); const _doc = await Model.collection.findOne(); assert.equal(_doc['undefined'][0], 'foo'); }); it('fires pre save hooks on nested child schemas (gh-7792)', function() { const childSchema1 = new mongoose.Schema({ name: String }); let called1 = 0; childSchema1.pre('save', function() { ++called1; }); const childSchema2 = new mongoose.Schema({ name: String }); let called2 = 0; childSchema2.pre('save', function() { ++called2; }); const parentSchema = new mongoose.Schema({ nested: { child: childSchema1, arr: [childSchema2] } }); const Parent = db.model('Parent', parentSchema); const obj = { nested: { child: { name: 'foo' }, arr: [{ name: 'bar' }] } }; return Parent.create(obj).then(() => { assert.equal(called1, 1); assert.equal(called2, 1); }); }); it('takes message from async custom validator promise rejection (gh-4913)', function() { const schema = new Schema({ name: { type: String, validate: async function() { await Promise.resolve((resolve) => setImmediate(resolve)); throw new Error('Oops!'); } } }); const Model = db.model('Test', schema); return Model.create({ name: 'foo' }).then(() => assert.ok(false), err => { assert.equal(err.errors['name'].message, 'Oops!'); assert.ok(err.message.indexOf('Oops!') !== -1, err.message); }); }); it('handles nested properties named `schema` (gh-7831)', async function() { const schema = new mongoose.Schema({ nested: { schema: String } }); const Model = db.model('Test', schema); await Model.collection.insertOne({ nested: { schema: 'test' } }); const doc = await Model.findOne(); assert.strictEqual(doc.nested.schema, 'test'); }); it('handles nested properties named `on` (gh-11656)', async function() { const schema = new mongoose.Schema({ on: String }, { supressReservedKeysWarning: true }); const Model = db.model('Test', schema); await Model.create({ on: 'test string' }); const doc = await Model.findOne(); assert.strictEqual(doc.on, 'test string'); }); describe('overwrite() (gh-7830)', function() { let Model; beforeEach(function() { const schema = new Schema({ _id: Number, name: String, nested: { prop: String }, arr: [Number], immutable: { type: String, immutable: true } }); Model = db.model('Test', schema); }); it('works', async function() { const doc = await Model.create({ _id: 1, name: 'test', nested: { prop: 'foo' }, immutable: 'bar' }); doc.overwrite({ name: 'test2' }); assert.deepEqual(doc.toObject(), { _id: 1, __v: 0, name: 'test2', immutable: 'bar' }); }); it('skips version key', async function() { await Model.collection.insertOne({ _id: 2, __v: 5, name: 'test', nested: { prop: 'foo' }, immutable: 'bar' }); const doc = await Model.findOne({ _id: 2 }); doc.overwrite({ _id: 2, name: 'test2' }); assert.deepEqual(doc.toObject(), { _id: 2, __v: 5, name: 'test2', immutable: 'bar' }); }); it('skips discriminator key', async function() { const D = Model.discriminator('D', Schema({ other: String })); await Model.collection.insertOne({ _id: 2, __v: 5, __t: 'D', name: 'test', nested: { prop: 'foo' }, immutable: 'bar', other: 'baz' }); const doc = await D.findOne({ _id: 2 }); doc.overwrite({ _id: 2, name: 'test2' }); assert.deepEqual(doc.toObject(), { _id: 2, __v: 5, __t: 'D', name: 'test2', immutable: 'bar' }); return doc.validate(); }); it('overwrites maps (gh-9549)', async function() { const schema = new Schema({ name: String, myMap: { type: Map, of: String } }); db.deleteModel(/Test/); const Test = db.model('Test', schema); let doc = new Test({ name: 'test', myMap: { a: 1, b: 2 } });
await doc.save(); doc = await Test.findById(doc); doc.overwrite({ name: 'test2', myMap: { b: 2, c: 3 } }); await doc.save(); doc = await Test.findById(doc); assert.deepEqual(Array.from(doc.toObject().myMap.values()), [2, 3]); }); }); it('copies virtuals from array subdocs when casting array of docs with same schema (gh-7898)', function() { const ChildSchema = new Schema({ name: String }, { _id: false, id: false }); ChildSchema.virtual('foo'). set(function(foo) { this.__foo = foo; }). get(function() { return this.__foo || 0; }); const ParentSchema = new Schema({ name: String, children: [ChildSchema] }, { _id: false, id: false }); const WrapperSchema = new Schema({ name: String, parents: [ParentSchema] }, { _id: false, id: false }); const Parent = db.model('Parent', ParentSchema); const Wrapper = db.model('Test', WrapperSchema); const data = { name: 'P1', children: [{ name: 'C1' }, { name: 'C2' }] }; const parent = new Parent(data); parent.children[0].foo = 123; const wrapper = new Wrapper({ name: 'test', parents: [parent] }); assert.equal(wrapper.parents[0].children[0].foo, 123); }); describe('immutable properties (gh-7671)', function() { let Model; beforeEach(function() { const schema = new Schema({ createdAt: { type: Date, immutable: true, default: new Date('6/1/2019') }, name: String }); Model = db.model('Test', schema); }); it('SchemaType#immutable()', function() { const schema = new Schema({ createdAt: { type: Date, default: new Date('6/1/2019') }, name: String }); assert.ok(!schema.path('createdAt').$immutable); schema.path('createdAt').immutable(true); assert.ok(schema.path('createdAt').$immutable); assert.equal(schema.path('createdAt').setters.length, 1); schema.path('createdAt').immutable(false); assert.ok(!schema.path('createdAt').$immutable); assert.equal(schema.path('createdAt').setters.length, 0); }); it('with save()', async function() { let doc = new Model({ name: 'Foo' }); assert.equal(doc.createdAt.toLocaleDateString('en-us'), '6/1/2019'); await doc.save(); doc = await Model.findOne({ createdAt: new Date('6/1/2019') }); doc.createdAt = new Date('6/1/2017'); assert.equal(doc.createdAt.toLocaleDateString('en-us'), '6/1/2019'); doc.set({ createdAt: new Date('6/1/2021') }); assert.equal(doc.createdAt.toLocaleDateString('en-us'), '6/1/2019'); await doc.save(); doc = await Model.findOne({ createdAt: new Date('6/1/2019') }); assert.ok(doc); }); it('with update', async function() { let doc = new Model({ name: 'Foo' }); assert.equal(doc.createdAt.toLocaleDateString('en-us'), '6/1/2019'); await doc.save(); const update = { createdAt: new Date('6/1/2020') }; await Model.updateOne({}, update); doc = await Model.findOne(); assert.equal(doc.createdAt.toLocaleDateString('en-us'), '6/1/2019'); const err = await Model.updateOne({}, update, { strict: 'throw' }). then(() => null, err => err); assert.equal(err.name, 'StrictModeError'); assert.ok(err.message.indexOf('createdAt') !== -1, err.message); }); it('conditional immutable (gh-8001)', async function() { const schema = new Schema({ name: String, test: { type: String, immutable: doc => doc.name === 'foo' } }); const Model = db.model('Test1', schema);
const doc1 = await Model.create({ name: 'foo', test: 'before' }); const doc2 = await Model.create({ name: 'bar', test: 'before' }); doc1.set({ test: 'after' }); doc2.set({ test: 'after' }); await doc1.save(); await doc2.save(); const fromDb1 = await Model.collection.findOne({ name: 'foo' }); const fromDb2 = await Model.collection.findOne({ name: 'bar' }); assert.equal(fromDb1.test, 'before'); assert.equal(fromDb2.test, 'after'); }); it('immutable with strict mode (gh-8149)', async function() { const schema = new mongoose.Schema({ name: String, yearOfBirth: { type: Number, immutable: true } }, { strict: 'throw' }); const Person = db.model('Person', schema); const joe = await Person.create({ name: 'Joe', yearOfBirth: 2001 }); joe.set({ yearOfBirth: 2002 }); const err = await joe.save().then(() => null, err => err); assert.ok(err); assert.equal(err.errors['yearOfBirth'].name, 'StrictModeError'); }); }); it('consistent post order traversal for array subdocs (gh-7929)', function() { const Grandchild = Schema({ value: Number }); const Child = Schema({ children: [Grandchild] }); const Parent = Schema({ children: [Child] }); const calls = []; Grandchild.pre('save', () => calls.push(1)); Child.pre('save', () => calls.push(2)); Parent.pre('save', () => calls.push(3)); const Model = db.model('Parent', Parent); return Model.create({ children: [{ children: [{ value: 3 }] }] }).then(() => { assert.deepEqual(calls, [1, 2, 3]); }); }); it('respects projection for getters (gh-7940)', async function() { const schema = new Schema({ foo: String, bar: { type: String, get: () => { return 'getter value'; } } }, { toObject: { getters: true } }); const Model = db.model('Test', schema);
await Model.create({ foo: 'test', bar: 'baz' }); const doc = await Model.findOne({ foo: 'test' }, 'foo'); assert.ok(!doc.toObject().bar); }); it('loads doc with a `once` property successfully (gh-7958)', async function() { const eventSchema = Schema({ once: { prop: String } }); const Event = db.model('Test', eventSchema);
await Event.create({ once: { prop: 'test' } }); const doc = await Event.findOne(); assert.equal(doc.once.prop, 'test'); }); it('caster that converts to Number class works (gh-8150)', async function() { const mySchema = new Schema({ id: { type: Number, set: value => new Number(value.valueOf()) } }); const MyModel = db.model('Test', mySchema); await MyModel.create({ id: 12345 }); const doc = await MyModel.findOne({ id: 12345 }); assert.ok(doc); }); it('handles objectids and decimals with strict: false (gh-7973)', async function() { const testSchema = Schema({}, { strict: false }); const Test = db.model('Test', testSchema); let doc = new Test({ testId: new mongoose.Types.ObjectId(), testDecimal: new mongoose.Types.Decimal128('1.23') }); assert.ok(doc.testId instanceof mongoose.Types.ObjectId); assert.ok(doc.testDecimal instanceof mongoose.Types.Decimal128);
await doc.save(); doc = await Test.collection.findOne(); assert.ok(doc.testId instanceof mongoose.Types.ObjectId); assert.ok(doc.testDecimal instanceof mongoose.Types.Decimal128); }); it('allows enum on array of array of strings (gh-7926)', function() { const schema = new Schema({ test: { type: [[String]], enum: ['bar'] } }); const Model = db.model('Test', schema); return Model.create({ test: [['foo']] }).then(() => assert.ok(false), err => { assert.ok(err); assert.ok(err.errors['test.0.0']); assert.ok(err.errors['test.0.0'].message.indexOf('foo') !== -1, err.errors['test.0.0'].message); }); }); it('allows saving an unchanged document if required populated path is null (gh-8018)', async function() { const schema = Schema({ test: String }); const schema2 = Schema({ keyToPopulate: { type: mongoose.Schema.Types.ObjectId, ref: 'Child', required: true } }); const Child = db.model('Child', schema); const Parent = db.model('Parent', schema2);
const child = await Child.create({ test: 'test' }); await Parent.create({ keyToPopulate: child._id }); await child.deleteOne(); const doc = await Parent.findOne().populate('keyToPopulate'); // Should not throw await doc.save(); }); it('only calls validator once on mixed validator (gh-8067)', function() { let called = 0; function validator() { ++called; return true; } const itemArray = new Schema({ timer: { time: { type: {}, validate: { validator: validator } } } }); const schema = new Schema({ items: [itemArray] }); const Model = db.model('Test', schema); const obj = new Model({ items: [ { timer: { time: { type: { hours: 24, allowed: true } } } } ] }); obj.validateSync(); assert.equal(called, 1); }); it('only calls validator once on nested mixed validator (gh-8117)', function() { const called = []; const Model = db.model('Test', Schema({ name: { type: String }, level1: { level2: { type: Object, validate: { validator: v => { called.push(v); return true; } } } } })); const doc = new Model({ name: 'bob' }); doc.level1 = { level2: { a: 'one', b: 'two', c: 'three' } }; return doc.validate().then(() => { assert.equal(called.length, 1); assert.deepEqual(called[0], { a: 'one', b: 'two', c: 'three' }); }); }); it('handles populate() with custom type that does not cast to doc (gh-8062)', async function() { class Gh8062 extends mongoose.SchemaType { cast(val) { if (typeof val === 'string') { return val; } throw new Error('Failed!'); } } mongoose.Schema.Types.Gh8062 = Gh8062; const schema = new Schema({ arr: [{ type: Gh8062, ref: 'Child' }] }); const Model = db.model('Test', schema); const Child = db.model('Child', Schema({ _id: Gh8062 }));
await Child.create({ _id: 'test' }); await Model.create({ arr: ['test'] }); const doc = await Model.findOne().populate('arr'); assert.ok(doc.populated('arr')); assert.equal(doc.arr[0]._id, 'test'); assert.ok(doc.arr[0].$__ != null); }); it('can inspect() on a document array (gh-8037)', function() { const subdocSchema = mongoose.Schema({ a: String }); const schema = mongoose.Schema({ subdocs: { type: [subdocSchema] } }); const Model = db.model('Test', schema); const data = { _id: new mongoose.Types.ObjectId(), subdocs: [{ a: 'a' }] }; const doc = new Model(); doc.init(data); require('util').inspect(doc.subdocs); }); it('always passes unpopulated paths to validators (gh-8042)', async function() { const schema = Schema({ test: String }); const calledWith = []; function validate(v) { calledWith.push(v); return true; } const schema2 = Schema({ keyToPopulate: { type: mongoose.Schema.Types.ObjectId, ref: 'gh8018_child', required: true, validate: validate }, array: [{ type: mongoose.Schema.Types.ObjectId, ref: 'gh8018_child', required: true, validate: validate }], subdoc: Schema({ keyToPopulate: { type: mongoose.Schema.Types.ObjectId, ref: 'gh8018_child', required: true, validate: validate } }) }); const Child = db.model('gh8018_child', schema); const Parent = db.model('gh8018_parent', schema2);
const child = await Child.create({ test: 'test' }); await Parent.create({ keyToPopulate: child, array: [child], subdoc: { keyToPopulate: child } }); assert.equal(calledWith.length, 3); assert.ok(calledWith[0] instanceof mongoose.Types.ObjectId); assert.ok(calledWith[1] instanceof mongoose.Types.ObjectId); assert.ok(calledWith[2] instanceof mongoose.Types.ObjectId); await child.deleteOne(); const doc = await Parent.findOne().populate(['keyToPopulate', 'array', 'subdoc']); assert.equal(doc.keyToPopulate, null); // Should not throw await doc.save(); }); it('set() merge option with single nested (gh-8201)', async function() { const AddressSchema = Schema({ street: { type: String, required: true }, city: { type: String, required: true } }); const PersonSchema = Schema({ name: { type: String, required: true }, address: { type: AddressSchema, required: true } }); const Person = db.model('Person', PersonSchema);
await Person.create({ name: 'John Smith', address: { street: 'Real Street', city: 'Somewhere' } }); const person = await Person.findOne(); const obj = { name: 'John Smythe', address: { street: 'Fake Street' } }; person.set(obj, undefined, { merge: true }); assert.equal(person.address.city, 'Somewhere'); await person.save(); }); it('setting single nested subdoc with timestamps (gh-8251)', async function() { const ActivitySchema = Schema({ description: String }, { timestamps: true }); const RequestSchema = Schema({ activity: ActivitySchema }); const Request = db.model('Test', RequestSchema);
const doc = await Request.create({ activity: { description: 'before' } }); doc.activity.set({ description: 'after' }); await doc.save(); const fromDb = await Request.findOne().lean(); assert.equal(fromDb.activity.description, 'after'); }); it('passing an object with toBSON() into `save()` (gh-8299)', async function() { const ActivitySchema = Schema({ description: String }); const RequestSchema = Schema({ activity: ActivitySchema }); const Request = db.model('Test', RequestSchema);
const doc = await Request.create({ activity: { description: 'before' } }); doc.activity.set({ description: 'after' }); await doc.save(); const fromDb = await Request.findOne().lean(); assert.equal(fromDb.activity.description, 'after'); }); it('handles getter setting virtual on manually populated doc when calling toJSON (gh-8295)', function() { const childSchema = Schema({}, { toJSON: { getters: true } }); childSchema.virtual('field'). get(function() { return this._field; }). set(function(v) { return this._field = v; }); const Child = db.model('Child', childSchema); const parentSchema = Schema({ child: { type: mongoose.ObjectId, ref: 'Child', get: get } }, { toJSON: { getters: true } }); const Parent = db.model('Parent', parentSchema); function get(child) { child.field = true; return child; } let p = new Parent({ child: new Child({}) }); assert.strictEqual(p.toJSON().child.field, true); p = new Parent({ child: new Child({}) }); assert.strictEqual(p.child.toJSON().field, true); }); it('enum validator for number (gh-8139)', function() { const schema = Schema({ num: { type: Number, enum: [1, 2, 3] } }); const Model = db.model('Test', schema); let doc = new Model({}); let err = doc.validateSync(); assert.ifError(err); doc = new Model({ num: 4 }); err = doc.validateSync(); assert.ok(err); assert.equal(err.errors['num'].name, 'ValidatorError'); doc = new Model({ num: 2 }); err = doc.validateSync(); assert.ifError(err); }); it('enum object syntax for number (gh-10648) (gh-8139)', function() { const schema = Schema({ num: { type: Number, enum: { values: [1, 2, 3], message: 'Invalid number' } } }); const Model = db.model('Test', schema); let doc = new Model({}); let err = doc.validateSync(); assert.ifError(err); doc = new Model({ num: 4 }); err = doc.validateSync(); assert.ok(err); assert.equal(err.errors['num'].name, 'ValidatorError'); assert.equal(err.errors['num'].message, 'Invalid number'); doc = new Model({ num: 2 }); err = doc.validateSync(); assert.ifError(err); }); it('support `pathsToValidate()` option for `validate()` (gh-7587)', async function() { const schema = Schema({ name: { type: String, required: true }, age: { type: Number, required: true }, rank: String }); const Model = db.model('Test', schema);
const doc = new Model({}); let err = await doc.validate(['name', 'rank']).catch(err => err); assert.deepEqual(Object.keys(err.errors), ['name']); err = await doc.validate(['age', 'rank']).catch(err => err); assert.deepEqual(Object.keys(err.errors), ['age']); }); it('array push with $position (gh-4322)', async function() { const schema = Schema({ nums: [Number] }); const Model = db.model('Test', schema);
const doc = await Model.create({ nums: [3, 4] }); doc.nums.push({ $each: [1, 2], $position: 0 }); assert.deepEqual(doc.toObject().nums, [1, 2, 3, 4]); await doc.save(); const fromDb = await Model.findOne({ _id: doc._id }); assert.deepEqual(fromDb.toObject().nums, [1, 2, 3, 4]); doc.nums.push({ $each: [0], $position: 0 }); assert.throws(() => { doc.nums.push({ $each: [5] }); }, /Cannot call.*multiple times/); assert.throws(() => { doc.nums.push(5); }, /Cannot call.*multiple times/); }); it('setting a path to a single nested document should update the single nested doc parent (gh-8400)', function() { const schema = Schema({ name: String, subdoc: new Schema({ name: String }) }); const Model = db.model('Test', schema); const doc1 = new Model({ name: 'doc1', subdoc: { name: 'subdoc1' } }); const doc2 = new Model({ name: 'doc2', subdoc: { name: 'subdoc2' } }); doc1.subdoc = doc2.subdoc; assert.equal(doc1.subdoc.name, 'subdoc2'); assert.equal(doc2.subdoc.name, 'subdoc2'); assert.strictEqual(doc1.subdoc.ownerDocument(), doc1); assert.strictEqual(doc2.subdoc.ownerDocument(), doc2); }); it('setting an array to an array with some populated documents depopulates the whole array (gh-8443)', async function() { const A = db.model('Test1', Schema({ name: String, rel: [{ type: mongoose.ObjectId, ref: 'Test' }] })); const B = db.model('Test', Schema({ name: String }));
const b = await B.create({ name: 'testb' }); await A.create({ name: 'testa', rel: [b._id] }); const a = await A.findOne().populate('rel'); const b2 = await B.create({ name: 'testb2' }); a.rel = [a.rel[0], b2._id]; await a.save(); assert.ok(!a.populated('rel')); assert.ok(a.rel[0] instanceof mongoose.Types.ObjectId); assert.ok(a.rel[1] instanceof mongoose.Types.ObjectId); }); it('handles errors with name set to "ValidationError" (gh-8466)', () => { const childSchema = Schema({ name: String }); childSchema.pre('validate', function() { if (this.name === 'Invalid') { const error = new Error('invalid name'); error.name = 'ValidationError'; throw error; } }); const fatherSchema = Schema({ children: [childSchema] }); const Father = db.model('Test', fatherSchema); const doc = new Father({ children: [{ name: 'Valid' }, { name: 'Invalid' }] }); return doc.validate().then(() => assert.ok(false), err => { assert.ok(err); assert.ok(err.errors['children']); assert.equal(err.errors['children'].message, 'invalid name'); }); }); it('throws an error if running validate() multiple times in parallel (gh-8468)', () => { const Model = db.model('Test', Schema({ name: String })); const doc = new Model({ name: 'test' }); doc.validate(); return doc.save().then(() => assert.ok(false), err => { assert.equal(err.name, 'ParallelValidateError'); }); }); it('avoids parallel validate error when validating nested path with double nested subdocs (gh-8486)', async function() { const testSchema = new Schema({ foo: { bar: Schema({ baz: Schema({ num: Number }) }) } }); const Test = db.model('Test', testSchema);
const doc = await Test.create({}); doc.foo = { bar: { baz: { num: 1 } } }; // Should not throw await doc.save(); const raw = await Test.collection.findOne(); assert.equal(raw.foo.bar.baz.num, 1); }); it('supports function for date min/max validator error (gh-8512)', function() { const schema = Schema({ startDate: { type: Date, required: true, min: [new Date('2020-01-01'), () => 'test'] } }); db.deleteModel(/Test/); const Model = db.model('Test', schema); const doc = new Model({ startDate: new Date('2019-06-01') }); const err = doc.validateSync(); assert.ok(err.errors['startDate']); assert.equal(err.errors['startDate'].message, 'test'); }); it('sets parent and ownerDocument correctly with document array default (gh-8509)', async function() { const locationSchema = Schema({ name: String, city: String }); const owners = []; // Middleware to set a default location name derived from the parent organization doc locationSchema.pre('validate', function(next) { const owner = this.ownerDocument(); owners.push(owner); if (this.isNew && !this.get('name') && owner.get('name')) { this.set('name', `${owner.get('name')} Office`); } next(); }); const organizationSchema = Schema({ name: String, // Having a default doc this way causes issues locations: { type: [locationSchema], default: [{}] } }); const Organization = db.model('Test', organizationSchema); const org = new Organization(); org.set('name', 'MongoDB'); await org.save(); assert.equal(owners.length, 1); assert.ok(owners[0] === org); assert.equal(org.locations[0].name, 'MongoDB Office'); }); it('doesnt add `null` if property is undefined with minimize false (gh-8504)', async function() { const minimize = false; const schema = Schema({ num: Number, beta: { type: String } }, { toObject: { virtuals: true, minimize: minimize }, toJSON: { virtuals: true, minimize: minimize } } ); const Test = db.model('Test', schema); const dummy1 = new Test({ num: 1, beta: null }); const dummy2 = new Test({ num: 2, beta: void 0 });
await dummy1.save(); await dummy2.save(); const res = await Test.find().lean().sort({ num: 1 }); assert.strictEqual(res[0].beta, null); assert.ok(!res[1].hasOwnProperty('beta')); }); it('creates document array defaults in forward order, not reverse (gh-8514)', function() { let num = 0; const schema = Schema({ arr: [{ val: { type: Number, default: () => ++num } }] }); const Model = db.model('Test', schema); const doc = new Model({ arr: [{}, {}, {}] }); assert.deepEqual(doc.toObject().arr.map(v => v.val), [1, 2, 3]); }); it('can call subdocument validate multiple times in parallel (gh-8539)', async function() { const schema = Schema({ arr: [{ val: String }], single: Schema({ val: String }) }); const Model = db.model('Test', schema);
const doc = new Model({ arr: [{ val: 'test' }], single: { val: 'test' } }); await Promise.all([doc.arr[0].validate(), doc.arr[0].validate()]); await Promise.all([doc.single.validate(), doc.single.validate()]); }); it('sets `Document#op` when calling `validate()` (gh-8439)', function() { const schema = Schema({ name: String }); const ops = []; schema.pre('validate', function() { ops.push(this.$op); }); schema.post('validate', function() { ops.push(this.$op); }); const Model = db.model('Test', schema); const doc = new Model({ name: 'test' }); const promise = doc.validate(); assert.equal(doc.$op, 'validate'); return promise.then(() => assert.deepEqual(ops, ['validate', 'validate'])); }); it('schema-level transform (gh-8403)', function() { const schema = Schema({ myDate: { type: Date, transform: v => v.getFullYear() }, dates: [{ type: Date, transform: v => v.getFullYear() }], arr: [{ myDate: { type: Date, transform: v => v.getFullYear() } }] }); const Model = db.model('Test', schema); const doc = new Model({ myDate: new Date('2015/06/01'), dates: [new Date('2016/06/01')], arr: [{ myDate: new Date('2017/06/01') }] }); assert.equal(doc.toObject({ transform: true }).myDate, '2015'); assert.equal(doc.toObject({ transform: true }).dates[0], '2016'); assert.equal(doc.toObject({ transform: true }).arr[0].myDate, '2017'); }); it('transforms nested paths (gh-9543)', function() { const schema = Schema({ nested: { date: { type: Date, transform: v => v.getFullYear() } } }); const Model = db.model('Test', schema); const doc = new Model({ nested: { date: new Date('2020-06-01') } }); assert.equal(doc.toObject({ transform: true }).nested.date, '2020'); }); it('handles setting numeric paths with single nested subdocs (gh-8583)', async function() { const placedItemSchema = Schema({ image: String }, { _id: false }); const subdocumentSchema = Schema({ placedItems: { 1: placedItemSchema, first: placedItemSchema } }); const Model = db.model('Test', subdocumentSchema);
const doc = await Model.create({ placedItems: { 1: { image: 'original' }, first: { image: 'original' } } }); doc.set({ 'placedItems.1.image': 'updated', 'placedItems.first.image': 'updated' }); await doc.save(); assert.equal(doc.placedItems['1'].image, 'updated'); const fromDb = await Model.findById(doc); assert.equal(fromDb.placedItems['1'].image, 'updated'); }); it('setting nested array path to non-nested array wraps values top-down (gh-8544)', function() { const positionSchema = mongoose.Schema({ coordinates: { type: [[Number]], required: true }, lines: { type: [[[Number]]], required: true } }); const Position = db.model('Test', positionSchema); const position = new Position(); position.coordinates = [1, 2]; position.lines = [3, 4]; const obj = position.toObject(); assert.deepEqual(obj.coordinates, [[1, 2]]); assert.deepEqual(obj.lines, [[[3, 4]]]); }); it('doesnt wrap empty nested array with insufficient depth', function() { const weekSchema = mongoose.Schema({ days: { type: [[[Number]]], required: true } }); const Week = db.model('Test', weekSchema); const emptyWeek = new Week(); emptyWeek.days = [[], [], [], [], [], [], []]; const obj = emptyWeek.toObject(); assert.deepEqual(obj.days, [[], [], [], [], [], [], []]); }); it('doesnt wipe out nested keys when setting nested key to empty object with minimize (gh-8565)', function() { const opts = { autoIndex: false, autoCreate: false }; const schema1 = Schema({ plaid: { nestedKey: String } }, opts); const schema2 = Schema({ plaid: { nestedKey: String } }, opts); const schema3 = Schema({ plaid: { nestedKey: String } }, opts); const Test1 = db.model('Test1', schema1); const Test2 = db.model('Test2', schema2); const Test3 = db.model('Test3', schema3); const doc1 = new Test1({}); assert.deepEqual(doc1.toObject({ minimize: false }).plaid, {}); const doc2 = new Test2({ plaid: doc1.plaid }); assert.deepEqual(doc2.toObject({ minimize: false }).plaid, {}); const doc3 = new Test3({}); doc3.set({ plaid: doc2.plaid }); assert.deepEqual(doc3.toObject({ minimize: false }).plaid, {}); }); it('allows calling `validate()` in post validate hook without causing parallel validation error (gh-8597)', async function() { const EmployeeSchema = Schema({ name: String, employeeNumber: { type: String, validate: v => v.length > 5 } }); let called = 0; EmployeeSchema.post('validate', function() { ++called; if (!this.employeeNumber && !this._employeeNumberRetrieved) { this.employeeNumber = '123456'; this._employeeNumberRetrieved = true; return this.validate(); } }); const Employee = db.model('Test', EmployeeSchema);
const e = await Employee.create({ name: 'foo' }); assert.equal(e.employeeNumber, '123456'); assert.ok(e._employeeNumberRetrieved); assert.equal(called, 2); }); it('sets defaults when setting single nested subdoc (gh-8603)', async function() { const nestedSchema = Schema({ name: String, status: { type: String, default: 'Pending' } }); const Test = db.model('Test', { nested: nestedSchema });
let doc = await Test.create({ nested: { name: 'foo' } }); assert.equal(doc.nested.status, 'Pending'); doc = await Test.findById(doc); assert.equal(doc.nested.status, 'Pending'); Object.assign(doc, { nested: { name: 'bar' } }); assert.equal(doc.nested.status, 'Pending'); await doc.save(); doc = await Test.findById(doc); assert.equal(doc.nested.status, 'Pending'); }); it('handles validating single nested paths when specified in `pathsToValidate` (gh-8626)', function() { const nestedSchema = Schema({ name: { type: String, validate: v => v.length > 2 }, age: { type: Number, validate: v => v < 200 } }); const schema = Schema({ nested: nestedSchema }); mongoose.deleteModel(/Test/); const Model = mongoose.model('Test', schema); const doc = new Model({ nested: { name: 'a', age: 9001 } }); return doc.validate(['nested.name']).then(() => assert.ok(false), err => { assert.ok(err.errors['nested.name']); assert.ok(!err.errors['nested.age']); }); }); it('copies immutable fields when constructing new doc from old doc (gh-8642)', function() { const schema = Schema({ name: { type: String, immutable: true } }); const Model = db.model('Test', schema); const doc = new Model({ name: 'test' }); doc.isNew = false; const newDoc = new Model(doc); assert.equal(newDoc.name, 'test'); }); it('can save nested array after setting (gh-8689)', async function() { const schema = new mongoose.Schema({ name: String, array: [[{ label: String, value: String }]] }); const MyModel = db.model('Test', schema);
const doc = await MyModel.create({ name: 'foo' }); doc.set({ 'array.0': [{ label: 'hello', value: 'world' }] }); await doc.save(); const updatedDoc = await MyModel.findOne({ _id: doc._id }); assert.equal(updatedDoc.array[0][0].label, 'hello'); assert.equal(updatedDoc.array[0][0].value, 'world'); }); it('handles validator errors on subdoc paths (gh-5226)', function() { const schema = Schema({ child: { type: Schema({ name: String }), validate: () => false }, children: { type: [{ name: String }], validate: () => false } }); const Model = db.model('Test', schema); const doc = new Model({ child: {}, children: [] }); return doc.validate().then(() => assert.ok(false), err => { assert.ok(err); assert.ok(err.errors); assert.ok(err.errors.child); assert.ok(err.errors.children); }); }); it('reports array cast error with index (gh-8888)', function() { const schema = Schema({ test: [Number] }, { autoIndex: false, autoCreate: false }); const Test = db.model('test', schema); const t = new Test({ test: [1, 'world'] }); const err = t.validateSync(); assert.ok(err); assert.ok(err.errors); assert.ok(err.errors['test.1']); }); it('sets defaults if setting nested path to empty object with minimize false (gh-8829)', function() { const cartSchema = Schema({ _id: 'String', item: { name: { type: 'String', default: 'Default Name' } } }, { minimize: false }); const Test = db.model('Test', cartSchema); const doc = new Test({ _id: 'foobar', item: {} }); return doc.save(). then(() => Test.collection.findOne()). then(doc => assert.equal(doc.item.name, 'Default Name')); }); it('clears cast errors when setting an array subpath (gh-9080)', function() { const userSchema = new Schema({ tags: [Schema.ObjectId] }); const User = db.model('User', userSchema); const user = new User({ tags: ['hey'] }); user.tags = []; const err = user.validateSync(); assert.ifError(err); }); it('saves successfully if you splice() a sliced array (gh-9011)', async function() { const childSchema = Schema({ values: [Number] }); const parentSchema = Schema({ children: [childSchema] }); const Parent = db.model('Parent', parentSchema);
await Parent.create({ children: [ { values: [1, 2, 3] }, { values: [4, 5, 6] } ] }); const parent = await Parent.findOne(); const copy = parent.children[0].values.slice(); copy.splice(1); await parent.save(); const _parent = await Parent.findOne(); assert.deepEqual(_parent.toObject().children[0].values, [1, 2, 3]); }); it('handles modifying a subpath of a nested array of documents (gh-8926)', async function() { const bookSchema = new Schema({ title: String }); const aisleSchema = new Schema({ shelves: [[bookSchema]] }); const librarySchema = new Schema({ aisles: [aisleSchema] }); const Library = db.model('Test', librarySchema);
await Library.create({ aisles: [{ shelves: [[{ title: 'Clean Code' }]] }] }); const library = await Library.findOne(); library.aisles[0].shelves[0][0].title = 'Refactoring'; await library.save(); const foundLibrary = await Library.findOne().lean(); assert.equal(foundLibrary.aisles[0].shelves[0][0].title, 'Refactoring'); }); it('Document#save accepts `timestamps` option (gh-8947) for update', async function() { // Arrange const userSchema = new Schema({ name: String }, { timestamps: true }); const User = db.model('User', userSchema); const createdUser = await User.create({ name: 'Hafez' }); const user = await User.findOne({ _id: createdUser._id }); // Act user.name = 'John'; await user.save({ timestamps: false }); // Assert assert.deepEqual(createdUser.updatedAt, user.updatedAt); }); it('Document#save accepts `timestamps` option (gh-8947) on inserting a new document', async function() { // Arrange const userSchema = new Schema({ name: String }, { timestamps: true }); const User = db.model('User', userSchema); const user = new User({ name: 'Hafez' }); // Act await user.save({ timestamps: false }); // Assert assert.ok(!user.createdAt); assert.ok(!user.updatedAt); }); it('Sets default when passing undefined as value for a key in a nested subdoc (gh-12102) (gh-9039)', async function() { const Test = db.model('Test', { nested: { prop: { type: String, default: 'some default value' } } }); const obj = { nested: { prop: undefined } }; const doc = await Test.create(obj); assert.equal(doc.nested.prop, 'some default value'); assert.deepStrictEqual(obj, { nested: { prop: undefined } }); }); it('allows accessing $locals when initializing (gh-9098)', function() { const personSchema = new mongoose.Schema({ name: { first: String, last: String } }); personSchema.virtual('fullName'). get(function() { return this.$locals.fullName; }). set(function(newFullName) { this.$locals.fullName = newFullName; }); const Person = db.model('Person', personSchema); const axl = new Person({ fullName: 'Axl Rose' }); assert.equal(axl.fullName, 'Axl Rose'); }); describe('Document#getChanges(...) (gh-9096)', function() { it('returns an empty object when there are no changes', async function() { const User = db.model('User', { name: String, age: Number, country: String }); const user = await User.create({ name: 'Hafez', age: 25, country: 'Egypt' }); const changes = user.getChanges(); assert.deepEqual(changes, {}); }); it('returns only the changed paths', async function() { const User = db.model('User', { name: String, age: Number, country: String }); const user = await User.create({ name: 'Hafez', age: 25, country: 'Egypt' }); user.country = undefined; user.age = 26; const changes = user.getChanges(); assert.deepEqual(changes, { $set: { age: 26 }, $unset: { country: 1 } }); }); }); it('supports skipping defaults on a document (gh-8271)', function() { const testSchema = new mongoose.Schema({ testTopLevel: { type: String, default: 'foo' }, testNested: { prop: { type: String, default: 'bar' } }, testArray: [{ prop: { type: String, default: 'baz' } }], testSingleNested: new Schema({ prop: { type: String, default: 'qux' } }) }); const Test = db.model('Test', testSchema); const doc = new Test({ testArray: [{}], testSingleNested: {} }, null, { defaults: false }); assert.ok(!doc.testTopLevel); assert.ok(!doc.testNested.prop); assert.ok(!doc.testArray[0].prop); assert.ok(!doc.testSingleNested.prop); }); it('throws an error when `transform` returns a promise (gh-9163)', function() { const userSchema = new Schema({ name: { type: String, transform: function() { return new Promise(() => {}); } } }); const User = db.model('User', userSchema); const user = new User({ name: 'Hafez' }); assert.throws(function() { user.toJSON(); }, /must be synchronous/); assert.throws(function() { user.toObject(); }, /must be synchronous/); }); it('uses strict equality when checking mixed paths for modifications (gh-9165)', function() { const schema = Schema({ obj: {} }); const Model = db.model('gh9165', schema); return Model.create({ obj: { key: '2' } }). then(doc => { doc.obj = { key: 2 }; assert.ok(doc.modifiedPaths().indexOf('obj') !== -1); return doc.save(); }). then(doc => Model.findById(doc)). then(doc => assert.strictEqual(doc.obj.key, 2)); }); it('supports `useProjection` option for `toObject()` (gh-9118)', function() { const authorSchema = new mongoose.Schema({ name: String, hiddenField: { type: String, select: false } }); const Author = db.model('Author', authorSchema); const example = new Author({ name: 'John', hiddenField: 'A secret' }); assert.strictEqual(example.toJSON({ useProjection: true }).hiddenField, void 0); }); it('clears out priorDoc after overwriting single nested subdoc (gh-9208)', async function() { const TestModel = db.model('Test', Schema({ nested: Schema({ myBool: Boolean, myString: String }) }));
const test = new TestModel(); test.nested = { myBool: true }; await test.save(); test.nested = { myString: 'asdf' }; await test.save(); test.nested.myBool = true; await test.save(); const doc = await TestModel.findById(test); assert.strictEqual(doc.nested.myBool, true); }); it('handles immutable properties underneath single nested subdocs when overwriting (gh-9281)', async function() { const SubSchema = Schema({ nestedProp: { type: String, immutable: true } }, { strict: 'throw' }); const TestSchema = Schema({ object: SubSchema }, { strict: 'throw' }); const Test = db.model('Test', TestSchema);
await Test.create({ object: { nestedProp: 'A' } }); const doc = await Test.findOne(); doc.object = {}; const err = await doc.save().then(() => null, err => err); assert.ok(err); assert.ok(err.errors['object']); assert.ok(err.message.includes('Path `nestedProp` is immutable'), err.message); // Setting to the same value as the previous doc is ok. doc.object = { nestedProp: 'A' }; await doc.save(); }); it('allows removing boolean key by setting it to `undefined` (gh-9275)', async function() { const Test = db.model('Test', Schema({ a: Boolean }));
const doc = await Test.create({ a: true }); doc.a = undefined; await doc.save(); const fromDb = await Test.findOne().lean(); assert.ok(!('a' in fromDb)); }); it('keeps manually populated paths when setting a nested path to itself (gh-9293)', async function() { const StepSchema = Schema({ ride: { type: ObjectId, ref: 'Ride' }, status: Number }); const RideSchema = Schema({ status: Number, steps: { taxi: [{ type: ObjectId, ref: 'Step' }], rent: [{ type: ObjectId, ref: 'Step' }], vehicle: [{ type: ObjectId, ref: 'Step' }] } }); const Step = db.model('Step', StepSchema); const Ride = db.model('Ride', RideSchema);
let ride = await Ride.create({ status: 0 }); const steps = await Step.create([ { ride: ride, status: 0 }, { ride: ride, status: 1 }, { ride: ride, status: 2 } ]); ride.steps = { taxi: [steps[0]], rent: [steps[1]], vehicle: [steps[2]] }; await ride.save(); ride = await Ride.findOne({}).populate('steps.taxi steps.vehicle steps.rent'); assert.equal(ride.steps.taxi[0].status, 0); assert.equal(ride.steps.rent[0].status, 1); assert.equal(ride.steps.vehicle[0].status, 2); ride.steps = ride.steps; assert.equal(ride.steps.taxi[0].status, 0); assert.equal(ride.steps.rent[0].status, 1); assert.equal(ride.steps.vehicle[0].status, 2); }); it('doesnt wipe out nested paths when setting a nested path to itself (gh-9313)', async function() { const schema = new Schema({ nested: { prop1: { type: Number, default: 50 }, prop2: { type: String, enum: ['val1', 'val2'], default: 'val1', required: true }, prop3: { prop4: { type: Number, default: 0 } } } }); const Model = db.model('Test', schema);
let doc = await Model.create({}); doc = await Model.findById(doc); doc.nested = doc.nested; assert.equal(doc.nested.prop2, 'val1'); await doc.save(); const fromDb = await Model.collection.findOne({ _id: doc._id }); assert.equal(fromDb.nested.prop2, 'val1'); }); it('allows saving after setting document array to itself (gh-9266)', async function() { const Model = db.model('Test', Schema({ keys: [{ _id: false, name: String }] }));
const document = new Model({}); document.keys[0] = { name: 'test' }; document.keys = document.keys; await document.save(); const fromDb = await Model.findOne(); assert.deepEqual(fromDb.toObject().keys, [{ name: 'test' }]); }); it('allows accessing document values from function default on array (gh-9351) (gh-6155)', function() { const schema = Schema({ publisher: String, authors: { type: [String], default: function() { return [this.publisher]; } } }); const Test = db.model('Test', schema); const doc = new Test({ publisher: 'Mastering JS' }); assert.deepEqual(doc.toObject().authors, ['Mastering JS']); }); it('handles pulling array subdocs when _id is an alias (gh-9319)', async function() { const childSchema = Schema({ field: { type: String, alias: '_id' } }, { _id: false }); const parentSchema = Schema({ children: [childSchema] }); const Parent = db.model('Parent', parentSchema);
await Parent.create({ children: [{ field: '1' }] }); const p = await Parent.findOne(); p.children.pull('1'); await p.save(); assert.equal(p.children.length, 0); const fromDb = await Parent.findOne(); assert.equal(fromDb.children.length, 0); }); it('allows setting nested path to instance of model (gh-9392)', function() { const def = { test: String }; const Child = db.model('Child', def); const Parent = db.model('Parent', { nested: def }); const c = new Child({ test: 'new' }); const p = new Parent({ nested: { test: 'old' } }); p.nested = c; assert.equal(p.nested.test, 'new'); }); it('unmarks modified if setting a value to the same value as it was previously (gh-9396)', async function() { const schema = new Schema({ bar: String }); const Test = db.model('Test', schema); const foo = new Test({ bar: 'bar' }); await foo.save(); assert.ok(!foo.isModified('bar')); foo.bar = 'baz'; assert.ok(foo.isModified('bar')); foo.bar = 'bar'; assert.ok(!foo.isModified('bar')); }); it('unmarks modified if setting a value to the same subdoc as it was previously (gh-9396)', async function() { const schema = new Schema({ nested: { bar: String }, subdoc: new Schema({ bar: String }, { _id: false }) }); const Test = db.model('Test', schema);
const foo = new Test({ nested: { bar: 'bar' }, subdoc: { bar: 'bar' } }); await foo.save(); assert.ok(!foo.isModified('nested')); assert.ok(!foo.isModified('subdoc')); foo.nested = { bar: 'baz' }; foo.subdoc = { bar: 'baz' }; assert.ok(foo.isModified('nested')); assert.ok(foo.isModified('subdoc')); foo.nested = { bar: 'bar' }; foo.subdoc = { bar: 'bar' }; assert.ok(!foo.isModified('nested')); assert.ok(!foo.isModified('subdoc')); assert.ok(!foo.isModified('subdoc.bar')); foo.nested = { bar: 'baz' }; foo.subdoc = { bar: 'baz' }; assert.ok(foo.isModified('nested')); assert.ok(foo.isModified('subdoc')); await foo.save(); foo.nested = { bar: 'bar' }; foo.subdoc = { bar: 'bar' }; assert.ok(foo.isModified('nested')); assert.ok(foo.isModified('subdoc')); assert.ok(foo.isModified('subdoc.bar')); }); it('correctly tracks saved state for deeply nested objects (gh-10773) (gh-9396)', async function() { const PaymentSchema = Schema({ status: String }, { _id: false }); const OrderSchema = new Schema({ status: String, payments: { payout: PaymentSchema } }); const Order = db.model('Order', OrderSchema); const order = new Order({ status: 'unpaid', payments: { payout: { status: 'unpaid' } } }); await order.save(); const newPaymentsStatus = Object.assign({}, order.payments); newPaymentsStatus.payout.status = 'paid'; order.payments = newPaymentsStatus; assert.ok(order.isModified('payments')); await order.save(); const fromDb = await Order.findById(order._id).lean(); assert.equal(fromDb.payments.payout.status, 'paid'); }); it('marks path as errored if default function throws (gh-9408)', function() { const jobSchema = new Schema({ deliveryAt: Date, subJob: [{ deliveryAt: Date, shippingAt: { type: Date, default: () => { throw new Error('Oops!'); } }, prop: { type: String, default: 'default' } }] }); const Job = db.model('Test', jobSchema); const doc = new Job({ subJob: [{ deliveryAt: new Date() }] }); assert.equal(doc.subJob[0].prop, 'default'); }); it('passes subdoc with initial values set to default function when init-ing (gh-9408)', function() { const jobSchema = new Schema({ deliveryAt: Date, subJob: [{ deliveryAt: Date, shippingAt: { type: Date, default: function() { return this.deliveryAt; } } }] }); const Job = db.model('Test', jobSchema); const date = new Date(); const doc = new Job({ subJob: [{ deliveryAt: date }] }); assert.equal(doc.subJob[0].shippingAt.valueOf(), date.valueOf()); }); it('passes document as an argument for `required` function in schema definition (gh-9433)', function() { let docFromValidation; const userSchema = new Schema({ name: { type: String, required: (doc) => { docFromValidation = doc; return doc.age > 18; } }, age: Number }); const User = db.model('User', userSchema); const user = new User({ age: 26 }); const err = user.validateSync(); assert.ok(err); assert.ok(docFromValidation === user); }); it('works with path named isSelected (gh-9438)', function() { const categorySchema = new Schema({ name: String, categoryUrl: { type: String, required: true }, // Makes test fail isSelected: Boolean }); const siteSchema = new Schema({ categoryUrls: [categorySchema] }); const Test = db.model('Test', siteSchema); const test = new Test({ categoryUrls: [ { name: 'A', categoryUrl: 'B', isSelected: false, isModified: false } ] }); const err = test.validateSync(); assert.ifError(err); }); it('init tracks cast error reason (gh-9448)', function() { const Test = db.model('Test', Schema({ num: Number })); const doc = new Test(); doc.init({ num: 'not a number' }); const err = doc.validateSync(); assert.ok(err.errors['num'].reason); }); it('correctly handles setting nested path underneath single nested subdocs (gh-9459)', function() { const preferencesSchema = mongoose.Schema({ notifications: { email: Boolean, push: Boolean }, keepSession: Boolean }, { _id: false }); const User = db.model('User', Schema({ email: String, username: String, preferences: preferencesSchema })); const userFixture = { email: 'foo@bar.com', username: 'foobars', preferences: { keepSession: true, notifications: { email: false, push: false } } }; let userWithEmailNotifications = Object.assign({}, userFixture, { 'preferences.notifications': { email: true } }); let testUser = new User(userWithEmailNotifications); assert.deepEqual(testUser.toObject().preferences.notifications, { email: true }); userWithEmailNotifications = Object.assign({}, userFixture, { 'preferences.notifications.email': true }); testUser = new User(userWithEmailNotifications); assert.deepEqual(testUser.toObject().preferences.notifications, { email: true, push: false }); }); it('$isValid() with space-delimited and array syntax (gh-9474)', function() { const Test = db.model('Test', Schema({ name: String, email: String, age: Number, answer: Number })); const doc = new Test({ name: 'test', email: 'test@gmail.com', age: 'bad', answer: 'bad' }); assert.ok(doc.$isValid('name')); assert.ok(doc.$isValid('email')); assert.ok(!doc.$isValid('age')); assert.ok(!doc.$isValid('answer')); assert.ok(doc.$isValid('name email')); assert.ok(doc.$isValid('name age')); assert.ok(!doc.$isValid('age answer')); assert.ok(doc.$isValid(['name', 'email'])); assert.ok(doc.$isValid(['name', 'age'])); assert.ok(!doc.$isValid(['age', 'answer'])); }); it('avoids overwriting array subdocument when setting dotted path that is not selected (gh-9427)', async function() { const Test = db.model('Test', Schema({ arr: [{ _id: false, val: Number }], name: String, age: Number }));
let doc = await Test.create({ name: 'Test', arr: [{ val: 1 }, { val: 2 }], age: 30 }); doc = await Test.findById(doc._id).select('name'); doc.set('arr.0.val', 2); await doc.save(); const fromDb = await Test.findById(doc._id); assert.deepEqual(fromDb.toObject().arr, [{ val: 2 }, { val: 2 }]); }); it('ignore getters when diffing objects for change tracking (gh-9501)', async function() { const schema = new Schema({ title: { type: String, required: true }, price: { type: Number, min: 0 }, taxPercent: { type: Number, required: function() { return this.price != null; }, min: 0, max: 100, get: value => value || 10 } }); const Test = db.model('Test', schema);
const doc = await Test.create({ title: 'original' }); doc.set({ title: 'updated', price: 10, taxPercent: 10 }); assert.ok(doc.modifiedPaths().indexOf('taxPercent') !== -1); await doc.save(); const fromDb = await Test.findById(doc).lean(); assert.equal(fromDb.taxPercent, 10); }); it('allows defining middleware for all document hooks using regexp (gh-9190)', async function() { const schema = Schema({ name: String }); let called = 0; schema.pre(/.*/, { document: true, query: false }, function() { ++called; }); const Model = db.model('Test', schema);
await Model.find(); assert.equal(called, 0); await Model.findOne(); assert.equal(called, 0); await Model.countDocuments(); assert.equal(called, 0); const docs = await Model.create([{ name: 'test' }], { validateBeforeSave: false }); assert.equal(called, 1); await docs[0].validate(); assert.equal(called, 2); await docs[0].updateOne({ name: 'test2' }); assert.equal(called, 3); await Model.aggregate([{ $match: { name: 'test' } }]); assert.equal(called, 3); }); it('correctly handles setting nested props to other nested props (gh-9519)', async function() { const schemaA = Schema({ propX: { nested1: { prop: Number }, nested2: { prop: Number }, nested3: { prop: Number } }, propY: { nested1: { prop: Number }, nested2: { prop: Number }, nested3: { prop: Number } } }); const schemaB = Schema({ prop: { prop: Number } }); const ModelA = db.model('Test1', schemaA); const ModelB = db.model('Test2', schemaB);
const saved = await ModelA.create({ propX: { nested1: { prop: 1 }, nested2: { prop: 1 }, nested3: { prop: 1 } }, propY: { nested1: { prop: 2 }, nested2: { prop: 2 }, nested3: { prop: 2 } } }); const objA = await ModelA.findById(saved._id); const objB = new ModelB(); objB.prop = objA.propX.nested1; assert.strictEqual(objB.prop.prop, 1); }); it('sets fields after an undefined field (gh-9585)', function() { const personSchema = new Schema({ items: { type: Array }, email: { type: String } }); const Person = db.model('Person', personSchema);
const person = new Person({ items: undefined, email: 'test@gmail.com' }); assert.equal(person.email, 'test@gmail.com'); }); it('passes document to `default` functions (gh-9633)', function() { let documentFromDefault; const userSchema = new Schema({ name: { type: String }, age: { type: Number, default: function(doc) { documentFromDefault = doc; } } }); const User = db.model('User', userSchema); const user = new User({ name: 'Hafez' }); assert.ok(documentFromDefault === user); assert.equal(documentFromDefault.name, 'Hafez'); }); it('handles pre hook throwing a sync error (gh-9659)', async function() { const TestSchema = new Schema({ name: String }); TestSchema.pre('save', function() { throw new Error('test err'); }); const TestModel = db.model('Test', TestSchema);
const testObject = new TestModel({ name: 't' }); const err = await testObject.save().then(() => null, err => err); assert.ok(err); assert.equal(err.message, 'test err'); }); it('returns undefined rather than entire object when calling `get()` with empty string (gh-9681)', function() { const TestSchema = new Schema({ name: String }); const TestModel = db.model('Test', TestSchema); const testObject = new TestModel({ name: 't' }); assert.strictEqual(testObject.get(''), void 0); }); it('keeps atomics when assigning array to filtered array (gh-9651)', async function() { const Model = db.model('Test', { arr: [{ abc: String }] });
const m1 = new Model({ arr: [{ abc: 'old' }] }); await m1.save(); const m2 = await Model.findOne({ _id: m1._id }); m2.arr = []; m2.arr = m2.arr.filter(() => true); m2.arr.push({ abc: 'ghi' }); await m2.save(); const fromDb = await Model.findById(m1._id); assert.equal(fromDb.arr.length, 1); assert.equal(fromDb.arr[0].abc, 'ghi'); }); it('does not pass doc to ObjectId or Date.now (gh-9633) (gh-9636)', function() { const userSchema = new Schema({ parentId: { type: Schema.ObjectId, ref: 'User', default: () => new mongoose.Types.ObjectId() }, createdAt: { type: Date, default: Date.now } }); const User = db.model('User', userSchema); const user = new User(); assert.ok(user.parentId instanceof mongoose.Types.ObjectId); assert.ok(user.createdAt instanceof Date); }); it('supports getting a list of populated docs (gh-9702)', async function() { const Child = db.model('Child', Schema({ name: String })); const Parent = db.model('Parent', { children: [{ type: ObjectId, ref: 'Child' }], child: { type: ObjectId, ref: 'Child' } });
const c = await Child.create({ name: 'test' }); await Parent.create({ children: [c._id], child: c._id }); const p = await Parent.findOne(); await p.populate('children'); await p.populate('child'); p.children; // [{ _id: '...', name: 'test' }] assert.equal(p.$getPopulatedDocs().length, 2); assert.equal(p.$getPopulatedDocs()[0], p.children[0]); assert.equal(p.$getPopulatedDocs()[0].name, 'test'); assert.equal(p.$getPopulatedDocs()[1], p.child); assert.equal(p.$getPopulatedDocs()[1].name, 'test'); }); it('with virtual populate (gh-10148)', async function() { const childSchema = Schema({ name: String, parentId: 'ObjectId' }); childSchema.virtual('parent', { ref: 'Parent', localField: 'parentId', foreignField: '_id', justOne: true }); const Child = db.model('Child', childSchema); const Parent = db.model('Parent', Schema({ name: String }));
const p = await Parent.create({ name: 'Anakin' }); await Child.create({ name: 'Luke', parentId: p._id }); const res = await Child.findOne().populate('parent'); assert.equal(res.parent.name, 'Anakin'); const docs = res.$getPopulatedDocs(); assert.equal(docs.length, 1); assert.equal(docs[0].name, 'Anakin'); }); it('handles paths named `db` (gh-9798)', async function() { const schema = new Schema({ db: String }); const Test = db.model('Test', schema);
const doc = await Test.create({ db: 'foo' }); doc.db = 'bar'; await doc.save(); await doc.deleteOne(); const _doc = await Test.findOne({ db: 'bar' }); assert.ok(!_doc); }); it('handles paths named `schema` gh-8798', async function() { const schema = new Schema({ schema: String, name: String }); const Test = db.model('Test', schema);
const doc = await Test.create({ schema: 'test', name: 'test' }); await doc.save(); assert.ok(doc); assert.equal(doc.schema, 'test'); assert.equal(doc.name, 'test'); const fromDb = await Test.findById(doc); assert.equal(fromDb.schema, 'test'); assert.equal(fromDb.name, 'test'); doc.schema = 'test2'; await doc.save(); await fromDb.remove(); doc.name = 'test3'; const err = await doc.save().then(() => null, err => err); assert.ok(err); assert.equal(err.name, 'DocumentNotFoundError'); }); it('handles nested paths named `schema` gh-8798', async function() { const schema = new Schema({ nested: { schema: String }, name: String }); const Test = db.model('Test', schema);
const doc = await Test.create({ nested: { schema: 'test' }, name: 'test' }); await doc.save(); assert.ok(doc); assert.equal(doc.nested.schema, 'test'); assert.equal(doc.name, 'test'); const fromDb = await Test.findById(doc); assert.equal(fromDb.nested.schema, 'test'); assert.equal(fromDb.name, 'test'); doc.nested.schema = 'test2'; await doc.save(); }); it('object setters will be applied for each object in array after populate (gh-9838)', async function() { const updatedElID = '123456789012345678901234'; const ElementSchema = new Schema({ name: 'string', nested: [{ type: Schema.Types.ObjectId, ref: 'Nested' }] }); const NestedSchema = new Schema({}); const Element = db.model('Test', ElementSchema); const NestedElement = db.model('Nested', NestedSchema);
const nes = new NestedElement({}); await nes.save(); const ele = new Element({ nested: [nes.id], name: 'test' }); await ele.save(); const ss = await Element.findById(ele._id).populate({ path: 'nested', model: NestedElement }); ss.nested = [updatedElID]; await ss.save(); assert.ok(typeof ss.nested[0] !== 'string'); assert.equal(ss.nested[0].toHexString(), updatedElID); }); it('gh9884', async function() {
const obi = new Schema({ eType: { type: String, required: true, uppercase: true }, eOrigin: { type: String, required: true }, eIds: [ { type: String } ] }, { _id: false }); const schema = new Schema({ name: String, description: String, isSelected: { type: Boolean, default: false }, wan: { type: [obi], default: undefined, required: true } }); const newDoc = { name: 'name', description: 'new desc', isSelected: true, wan: [ { eType: 'X', eOrigin: 'Y', eIds: ['Y', 'Z'] } ] }; const Model = db.model('Test', schema); await Model.create(newDoc); const doc = await Model.findOne(); assert.ok(doc); }); it('Makes sure pre remove hook is executed gh-9885', async function() { const SubSchema = new Schema({ myValue: { type: String } }, {}); let count = 0; SubSchema.pre('remove', function(next) { count++; next(); }); const thisSchema = new Schema({ foo: { type: String, required: true }, mySubdoc: { type: [SubSchema], required: true } }, { minimize: false, collection: 'test' }); const Model = db.model('TestModel', thisSchema);
await Model.deleteMany({}); // remove all existing documents const newModel = { foo: 'bar', mySubdoc: [{ myValue: 'some value' }] }; const document = await Model.create(newModel); document.mySubdoc[0].remove(); await document.save().catch((error) => { console.error(error); }); assert.equal(count, 1); }); it('gh9880', function(done) { const testSchema = new Schema({ prop: String, nestedProp: { prop: String } }); const Test = db.model('Test', testSchema); new Test({ prop: 'Test', nestedProp: null }).save((err, doc) => { doc.id; doc.nestedProp; // let's clone this document: new Test({ prop: 'Test 2', nestedProp: doc.nestedProp }); Test.updateOne({ _id: doc._id }, { nestedProp: null }, (err) => { assert.ifError(err); Test.findOne({ _id: doc._id }, (err, updatedDoc) => { assert.ifError(err); new Test({ prop: 'Test 3', nestedProp: updatedDoc.nestedProp }); done(); }); }); }); }); it('handles directly setting embedded document array element with projection (gh-9909)', async function() { const schema = Schema({ elements: [{ text: String, subelements: [{ text: String }] }] }); const Test = db.model('Test', schema);
let doc = await Test.create({ elements: [{ text: 'hello' }] }); doc = await Test.findById(doc).select('elements'); doc.elements[0].subelements[0] = { text: 'my text' }; await doc.save(); const fromDb = await Test.findById(doc).lean(); assert.equal(fromDb.elements.length, 1); assert.equal(fromDb.elements[0].subelements.length, 1); assert.equal(fromDb.elements[0].subelements[0].text, 'my text'); }); it('toObject() uses child schema `flattenMaps` option by default (gh-9995)', async function() { const MapSchema = new Schema({ value: { type: Number } }, { _id: false }); const ChildSchema = new Schema({ map: { type: Map, of: MapSchema } }); ChildSchema.set('toObject', { flattenMaps: true }); const ParentSchema = new Schema({ child: { type: Schema.ObjectId, ref: 'Child' } }); const ChildModel = db.model('Child', ChildSchema); const ParentModel = db.model('Parent', ParentSchema);
const childDocument = new ChildModel({ map: { first: { value: 1 }, second: { value: 2 } } }); await childDocument.save(); const parentDocument = new ParentModel({ child: childDocument }); await parentDocument.save(); const resultDocument = await ParentModel.findOne().populate('child').exec(); let resultObject = resultDocument.toObject(); assert.ok(resultObject.child.map); assert.ok(!(resultObject.child.map instanceof Map)); resultObject = resultDocument.toObject({ flattenMaps: false }); assert.ok(resultObject.child.map instanceof Map); }); it('does not double validate paths under mixed objects (gh-10141)', async function() { let validatorCallCount = 0; const Test = db.model('Test', Schema({ name: String, object: { type: Object, validate: () => { validatorCallCount++; return true; } } }));
const doc = await Test.create({ name: 'test', object: { answer: 42 } }); validatorCallCount = 0; doc.set('object.question', 'secret'); doc.set('object.answer', 0); await doc.validate(); assert.equal(validatorCallCount, 0); }); it('clears child document modified when setting map path underneath single nested (gh-10295)', async function() { const SecondMapSchema = new mongoose.Schema({ data: { type: Map, of: Number, default: {}, _id: false } }); const FirstMapSchema = new mongoose.Schema({ data: { type: Map, of: SecondMapSchema, default: {}, _id: false } }); const NestedSchema = new mongoose.Schema({ data: { type: Map, of: SecondMapSchema, default: {}, _id: false } }); const TestSchema = new mongoose.Schema({ _id: Number, firstMap: { type: Map, of: FirstMapSchema, default: {}, _id: false }, nested: { type: NestedSchema, default: {}, _id: false } }); const Test = db.model('Test', TestSchema);
const doc = await Test.create({ _id: Date.now() }); doc.nested.data.set('second', {}); assert.ok(doc.modifiedPaths().indexOf('nested.data.second') !== -1, doc.modifiedPaths()); await doc.save(); doc.nested.data.get('second').data.set('final', 3); assert.ok(doc.modifiedPaths().indexOf('nested.data.second.data.final') !== -1, doc.modifiedPaths()); await doc.save(); const fromDb = await Test.findById(doc).lean(); assert.equal(fromDb.nested.data.second.data.final, 3); }); it('avoids infinite recursion when setting single nested subdoc to array (gh-10351)', async function() { const userInfoSchema = new mongoose.Schema({ _id: String }, { _id: false }); const observerSchema = new mongoose.Schema({ user: {} }, { _id: false }); const entrySchema = new mongoose.Schema({ creator: userInfoSchema, observers: [observerSchema] }); entrySchema.pre('save', function(next) { this.observers = [{ user: this.creator }]; next(); }); const Test = db.model('Test', entrySchema);
const entry = new Test({ creator: { _id: 'u1' } }); await entry.save(); const fromDb = await Test.findById(entry); assert.equal(fromDb.observers.length, 1); }); describe('reserved keywords can be used optionally (gh-9010)', () => { describe('Document#validate(...)', () => { it('is available as `$validate`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam' }); const err = await user.$validate(); assert.ok(err == null); assert.equal(user.$validate, user.validate); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: String, validate: Boolean }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam', validate: true }); assert.equal(user.validate, true); }); }); describe('Document#save(...)', () => { it('is available as `$save`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam' }); const userFromSave = await user.$save(); assert.ok(userFromSave === user); assert.equal(user.$save, user.save); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: String, save: Boolean }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam', save: true }); assert.equal(user.save, true); }); }); describe('Document#isModified(...)', () => { it('is available as `$isModified`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam' }); await user.save(); assert.ok(user.$isModified() === false); user.name = 'John'; assert.ok(user.$isModified() === true); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: String, isModified: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam', isModified: 'nope' }); assert.equal(user.isModified, 'nope'); }); }); describe('Document#isNew', () => { it('is available as `$isNew`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam' }); assert.ok(user.$isNew === true); await user.save(); assert.ok(user.$isNew === false); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: String, isNew: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam', isNew: 'yep' }); assert.equal(user.isNew, 'yep'); }); }); describe('Document#populated(...)', () => { it('is available as `$populated`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const postSchema = new Schema({ title: String, userId: { type: Schema.ObjectId, ref: 'User' } }); const Post = db.model('Post', postSchema); const user = await User.create({ name: 'Sam' }); const postFromCreate = await Post.create({ title: 'I am a title', userId: user._id }); const post = await Post.findOne({ _id: postFromCreate }).populate({ path: 'userId' }); assert.ok(post.$populated('userId')); post.depopulate('userId'); assert.ok(!post.$populated('userId')); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: String, populated: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam', populated: 'yep' }); assert.equal(user.populated, 'yep'); }); }); describe('Document#toObject(...)', () => { it('is available as `$toObject`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = await User.create({ name: 'Sam' }); assert.deepEqual(user.$toObject(), user.toObject()); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: String, toObject: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam', toObject: 'yep' }); assert.equal(user.toObject, 'yep'); }); }); describe('Document#init(...)', () => { it('is available as `$init`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = new User(); const sam = new User({ name: 'Sam' }); assert.equal(user.$init(sam).name, 'Sam'); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: String, init: Number }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam', init: 12 }); assert.equal(user.init, 12); }); }); xdescribe('Document#collection', () => { it('is available as `$collection`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = await User.create({ name: 'Hafez' }); const userFromCollection = await user.$collection.findOne({ _id: user._id }); assert.ok(userFromCollection); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ collection: Number }); const User = db.model('User', userSchema); const user = new User({ collection: 12 }); assert.equal(user.collection, 12); assert.ok(user.$collection !== user.collection); assert.ok(user.$collection); }); }); describe('Document#errors', () => { it('is available as `$errors`', async() => { const userSchema = new Schema({ name: { type: String, required: true } }); const User = db.model('User', userSchema); const user = new User(); user.validateSync(); assert.ok(user.$errors.name.kind === 'required'); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: { type: String, required: true }, errors: Number }); const User = db.model('User', userSchema); const user = new User({ errors: 12 }); user.validateSync(); assert.equal(user.errors, 12); assert.ok(user.$errors.name.kind === 'required'); }); }); describe('Document#removeListener', () => { it('is available as `$removeListener`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Hafez' }); assert.ok(user.$removeListener('save', () => {})); assert.ok(user.$removeListener === user.removeListener); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: { type: String, required: true }, removeListener: Number }); const User = db.model('User', userSchema); const user = new User({ removeListener: 12 }); assert.equal(user.removeListener, 12); }); }); describe('Document#listeners', () => { it('is available as `$listeners`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Hafez' }); assert.ok(user.$listeners === user.listeners); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: { type: String, required: true }, listeners: Number }); const User = db.model('User', userSchema); const user = new User({ listeners: 12 }); assert.equal(user.listeners, 12); }); }); describe('Document#on', () => { it('is available as `$on`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Hafez' }); assert.ok(user.$on === user.on); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: { type: String, required: true }, on: Number }); const User = db.model('User', userSchema); const user = new User({ on: 12 }); assert.equal(user.on, 12); }); }); describe('Document#emit', () => { it('is available as `$emit`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Hafez' }); assert.ok(user.$emit === user.emit); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: { type: String, required: true }, emit: Number }); const User = db.model('User', userSchema); const user = new User({ emit: 12 }); assert.equal(user.emit, 12); }); }); describe('Document#get', () => { it('is available as `$get`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Hafez' }); assert.ok(user.$get === user.get); }); it('can be used as a property in documents', () => { const userSchema = new Schema({ name: { type: String, required: true }, get: Number }); const User = db.model('User', userSchema); const user = new User({ get: 12 }); assert.equal(user.get, 12); }); }); describe('Document#remove', () => { it('is available as `$remove`', async() => { const userSchema = new Schema({ name: String }); const User = db.model('User', userSchema); const user = new User({ name: 'Hafez' }); await user.save(); await user.$remove(); const userFromDB = await User.findOne({ _id: user._id }); assert.ok(userFromDB == null); }); it('can be used as a property in documents', async() => { const userSchema = new Schema({ remove: Number }); const User = db.model('User', userSchema); const user = new User({ remove: 12 }); assert.equal(user.remove, 12); await user.save(); await user.$remove(); const userFromDB = await User.findOne({ _id: user._id }); assert.ok(userFromDB == null); }); }); }); describe('virtuals `pathsToSkip` (gh-10120)', () => { it('adds support for `pathsToSkip` for virtuals feat-10120', function() { const schema = new mongoose.Schema({ name: String, age: Number, nested: { test: String } }); schema.virtual('nameUpper').get(function() { return this.name.toUpperCase(); }); schema.virtual('answer').get(() => 42); schema.virtual('nested.hello').get(() => 'world'); const Model = db.model('Person', schema); const doc = new Model({ name: 'Jean-Luc Picard', age: 59, nested: { test: 'hello' } }); let obj = doc.toObject({ virtuals: { pathsToSkip: ['answer'] } }); assert.ok(obj.nameUpper); assert.equal(obj.answer, null); assert.equal(obj.nested.hello, 'world'); obj = doc.toObject({ virtuals: { pathsToSkip: ['nested.hello'] } }); assert.equal(obj.nameUpper, 'JEAN-LUC PICARD'); assert.equal(obj.answer, 42); assert.equal(obj.nested.hello, null); }); it('supports passing a list of virtuals to `toObject()` (gh-10120)', function() { const schema = new mongoose.Schema({ name: String, age: Number, nested: { test: String } }); schema.virtual('nameUpper').get(function() { return this.name.toUpperCase(); }); schema.virtual('answer').get(() => 42); schema.virtual('nested.hello').get(() => 'world'); const Model = db.model('Person', schema); const doc = new Model({ name: 'Jean-Luc Picard', age: 59, nested: { test: 'hello' } }); let obj = doc.toObject({ virtuals: true }); assert.equal(obj.nameUpper, 'JEAN-LUC PICARD'); assert.equal(obj.answer, 42); assert.equal(obj.nested.hello, 'world'); obj = doc.toObject({ virtuals: ['answer'] }); assert.ok(!obj.nameUpper); assert.equal(obj.answer, 42); assert.equal(obj.nested.hello, null); obj = doc.toObject({ virtuals: ['nameUpper'] }); assert.equal(obj.nameUpper, 'JEAN-LUC PICARD'); assert.equal(obj.answer, null); assert.equal(obj.nested.hello, null); obj = doc.toObject({ virtuals: ['nested.hello'] }); assert.equal(obj.nameUpper, null); assert.equal(obj.answer, null); assert.equal(obj.nested.hello, 'world'); }); }); describe('validation `pathsToSkip` (gh-10230)', () => { it('support `pathsToSkip` option for `Document#validate()`', async function() { const User = getUserModel(); const user = new User(); const err1 = await user.validate({ pathsToSkip: ['age'] }).then(() => null, err => err); assert.deepEqual(Object.keys(err1.errors), ['name']); const err2 = await user.validate({ pathsToSkip: ['name'] }).then(() => null, err => err); assert.deepEqual(Object.keys(err2.errors), ['age']); }); it('support `pathsToSkip` option for `Document#validate()`', async function() { const User = getUserModel(); const user = new User(); const err1 = await user.validate({ pathsToSkip: ['age'] }).then(() => null, err => err); assert.deepEqual(Object.keys(err1.errors), ['name']); const err2 = await user.validate({ pathsToSkip: ['name'] }).then(() => null, err => err); assert.deepEqual(Object.keys(err2.errors), ['age']); }); it('support `pathsToSkip` option for `Document#validateSync()`', () => { const User = getUserModel(); const user = new User(); const err1 = user.validateSync({ pathsToSkip: ['age'] }); assert.deepEqual(Object.keys(err1.errors), ['name']); const err2 = user.validateSync({ pathsToSkip: ['name'] }); assert.deepEqual(Object.keys(err2.errors), ['age']); }); // skip until gh-10367 is implemented xit('support `pathsToSkip` option for `Model.validate()`', async() => { const User = getUserModel(); const err1 = await User.validate({}, { pathsToSkip: ['age'] }); assert.deepEqual(Object.keys(err1.errors), ['name']); const err2 = await User.validate({}, { pathsToSkip: ['name'] }); assert.deepEqual(Object.keys(err2.errors), ['age']); }); it('`pathsToSkip` accepts space separated paths', async() => { const userSchema = Schema({ name: { type: String, required: true }, age: { type: Number, required: true }, country: { type: String, required: true }, rank: { type: String, required: true } }); const User = db.model('User', userSchema); const user = new User({ name: 'Sam', age: 26 }); const err1 = user.validateSync({ pathsToSkip: 'country rank' }); assert.ok(err1 == null); const err2 = await user.validate({ pathsToSkip: 'country rank' }).then(() => null, err => err); assert.ok(err2 == null); });
function getUserModel() { const userSchema = Schema({ name: { type: String, required: true }, age: { type: Number, required: true }, rank: String }); const User = db.model('User', userSchema); return User; } }); it('skips recursive merging (gh-9121)', function() { // Subdocument const subdocumentSchema = new mongoose.Schema({ child: new mongoose.Schema({ name: String, age: Number }, { _id: false }) }); const Subdoc = mongoose.model('Subdoc', subdocumentSchema); // Nested path const nestedSchema = new mongoose.Schema({ child: { name: String, age: Number } }); const Nested = mongoose.model('Nested', nestedSchema); const doc1 = new Subdoc({ child: { name: 'Luke', age: 19 } }); doc1.set({ child: { age: 21 } }); assert.deepEqual(doc1.toObject().child, { age: 21 }); const doc2 = new Nested({ child: { name: 'Luke', age: 19 } }); doc2.set({ child: { age: 21 } }); assert.deepEqual(doc2.toObject().child, { age: 21 }); }); it('does not pull non-schema paths from parent documents into nested paths (gh-10449)', function() { const schema = new Schema({ name: String, nested: { data: String } }); const Test = db.model('Test', schema); const doc = new Test({}); doc.otherProp = 'test'; assert.ok(!doc.nested.otherProp); }); it('sets properties in the order they are defined in the schema (gh-4665)', async function() { const schema = new Schema({ test: String, internal: { status: String, createdAt: Date }, profile: { name: { first: String, last: String } } }); const Test = db.model('Test', schema);
const doc = new Test({ profile: { name: { last: 'Musashi', first: 'Miyamoto' } }, internal: { createdAt: new Date('1603-06-01'), status: 'approved' }, test: 'test' }); assert.deepEqual(Object.keys(doc.toObject()), ['test', 'internal', 'profile', '_id']); assert.deepEqual(Object.keys(doc.toObject().profile.name), ['first', 'last']); assert.deepEqual(Object.keys(doc.toObject().internal), ['status', 'createdAt']); await doc.save(); const res = await Test.findOne({ _id: doc._id, 'profile.name': { first: 'Miyamoto', last: 'Musashi' } }); assert.ok(res); }); it('depopulate all should depopulate nested array population (gh-10592)', async function() { const Person = db.model('Person', { name: String }); const Band = db.model('Band', { name: String, members: [{ type: Schema.Types.ObjectId, ref: 'Person' }], lead: { type: Schema.Types.ObjectId, ref: 'Person' }, embeddedMembers: [{ active: Boolean, member: { type: Schema.Types.ObjectId, ref: 'Person' } }] }); const people = [{ name: 'Axl Rose' }, { name: 'Slash' }]; const docs = await Person.create(people); let band = { name: 'Guns N\' Roses', members: [docs[0]._id, docs[1]], lead: docs[0]._id, embeddedMembers: [{ active: true, member: docs[0]._id }, { active: false, member: docs[1]._id }] }; band = await Band.create(band); await band.populate('members lead embeddedMembers.member'); assert.ok(band.populated('members')); assert.ok(band.populated('lead')); assert.ok(band.populated('embeddedMembers.member')); assert.equal(band.members[0].name, 'Axl Rose'); assert.equal(band.embeddedMembers[0].member.name, 'Axl Rose'); band.depopulate(); assert.ok(!band.populated('members')); assert.ok(!band.populated('lead')); assert.ok(!band.populated('embeddedMembers.member')); assert.ok(!band.embeddedMembers[0].member.name); }); it('should allow dashes in the path name (gh-10677)', async function() { const schema = new mongoose.Schema({ values: { type: Map, of: { entries: String }, default: {} } }); const Model = db.model('test', schema, 'test'); const saved = new Model({}); await saved.save(); const document = await Model.findById({ _id: saved._id }); document.values.set('abc', { entries: 'a' }); document.values.set('abc-d', { entries: 'b' }); await document.save(); }); it('inits non-schema values if strict is false (gh-10828)', function() { const FooSchema = new Schema({}, { id: false, _id: false, strict: false }); const BarSchema = new Schema({ name: String, foo: FooSchema }); const Test = db.model('Test', BarSchema); const doc = new Test(); doc.init({ name: 'Test', foo: { something: 'A', other: 2 } }); assert.strictEqual(doc.foo.something, 'A'); assert.strictEqual(doc.foo.other, 2); }); it('avoids depopulating when setting array of subdocs from different doc (gh-10819)', function() { const Model1 = db.model('Test', Schema({ someField: String })); const Model2 = db.model('Test2', Schema({ subDocuments: [{ subDocument: { type: 'ObjectId', ref: 'Test' } }] })); const doc1 = new Model1({ someField: '111' }); const doc2 = new Model2({ subDocuments: { subDocument: doc1 } }); const doc3 = new Model2(doc2); assert.ok(doc3.populated('subDocuments.subDocument')); assert.equal(doc3.subDocuments[0].subDocument.someField, '111'); const doc4 = new Model2(); doc4.subDocuments = doc2.subDocuments; assert.ok(doc4.populated('subDocuments.subDocument')); assert.equal(doc4.subDocuments[0].subDocument.someField, '111'); }); it('allows validating doc again if pre validate errors out (gh-10830)', async function() { const BookSchema = Schema({ name: String, price: Number, quantity: Number }); BookSchema.pre('validate', disallownumflows); const Book = db.model('Test', BookSchema); function disallownumflows(next) { const self = this; if (self.isNew) return next(); if (self.quantity === 27) { return next(new Error('Wrong Quantity')); } next(); } const { _id } = await Book.create({ name: 'Hello', price: 50, quantity: 25 }); const doc = await Book.findById(_id); doc.quantity = 27; const err = await doc.save().then(() => null, err => err); assert.ok(err); doc.quantity = 26; await doc.save(); }); it('ensures that doc.ownerDocument() and doc.parent() by default return this on the root document (gh-10884)', async function() { const userSchema = new mongoose.Schema({ name: String, email: String }); const Event = db.model('Rainbow', userSchema); const e = new Event({ name: 'test' }); assert.strictEqual(e, e.parent()); assert.strictEqual(e, e.ownerDocument()); }); it('catches errors in `required` functions (gh-10968)', async function() { const TestSchema = new Schema({ url: { type: String, required: function() { throw new Error('oops!'); } } }); const Test = db.model('Test', TestSchema); const err = await Test.create({}).then(() => null, err => err); assert.ok(err); assert.equal(err.errors['url'].message, 'oops!'); }); it('does not allow overwriting schema methods with strict: false (gh-11001)', async function() { const TestSchema = new Schema({ text: { type: String, default: 'text' } }, { strict: false }); TestSchema.methods.someFn = () => 'good'; const Test = db.model('Test', TestSchema); const unTrusted = { someFn: () => 'bad' }; let x = await Test.create(unTrusted); await x.save(); assert.equal(x.someFn(), 'good'); x = new Test(unTrusted); await x.save(); assert.equal(x.someFn(), 'good'); x = await Test.create({}); await x.set(unTrusted); assert.equal(x.someFn(), 'good'); }); it('allows setting nested to instance of document (gh-11011)', async function() { const TransactionSchema = new Schema({ payments: [ { id: { type: String }, terminal: { _id: { type: Schema.Types.ObjectId }, name: { type: String } } } ] }); const TerminalSchema = new Schema({ name: { type: String }, apiKey: { type: String } }); const Transaction = db.model('Test1', TransactionSchema); const Terminal = db.model('Test2', TerminalSchema); const transaction = new Transaction(); const terminal = new Terminal({ name: 'Front desk', apiKey: 'somesecret' }); transaction.payments.push({ id: 'testPayment', terminal: terminal }); assert.equal(transaction.payments[0].terminal.name, 'Front desk'); }); it('cleans modified paths on deeply nested subdocuments (gh-11060)', async function() { const childSchema = new Schema({ status: String }); const deploymentsSchema = new Schema({ before: { type: childSchema, required: false }, after: { type: childSchema, required: false } }, { _id: false }); const testSchema = new Schema({ name: String, deployments: { type: deploymentsSchema } }); const Test = db.model('Test', testSchema); await Test.create({ name: 'hello', deployments: { before: { status: 'foo' } } }); const entry = await Test.findOne({ name: 'hello' }); const deployment = entry.deployments.before; deployment.status = 'bar'; entry.deployments.before = null; entry.deployments.after = deployment; assert.ok(!entry.isDirectModified('deployments.before.status')); await entry.save(); }); it('can manually populate subdocument refs (gh-10856)', async function() { // Bar model, has a name property and some other properties that we are interested in const BarSchema = new Schema({ name: String, more: String, another: Number }); const Bar = db.model('Bar', BarSchema); // Denormalised Bar schema with just the name, for use on the Foo model const BarNameSchema = new Schema({ _id: { type: Schema.Types.ObjectId, ref: 'Bar' }, name: String }); // Foo model, which contains denormalized bar data (just the name) const FooSchema = new Schema({ something: String, other: Number, bar: { type: BarNameSchema, ref: 'Bar' } }); const Foo = db.model('Foo', FooSchema); const bar2 = await Bar.create({ name: 'I am another Bar', more: 'With even more data', another: 3 }); const foo2 = await Foo.create({ something: 'I am another Foo', other: 4 }); foo2.bar = bar2; assert.ok(foo2.bar instanceof Bar); assert.equal(foo2.bar.another, 3); assert.equal(foo2.get('bar.another'), 3); const obj = foo2.toObject({ depopulate: true }); assert.equal(obj.bar.name, 'I am another Bar'); assert.strictEqual(obj.bar.another, undefined); await foo2.save(); const fromDb = await Foo.findById(foo2).lean(); assert.strictEqual(fromDb.bar.name, 'I am another Bar'); assert.strictEqual(fromDb.bar.another, undefined); }); it('can manually populate subdocument refs in `create()` (gh-10856)', async function() { // Bar model, has a name property and some other properties that we are interested in const BarSchema = new Schema({ name: String, more: String, another: Number }); const Bar = db.model('Bar', BarSchema); // Denormalised Bar schema with just the name, for use on the Foo model const BarNameSchema = new Schema({ _id: { type: Schema.Types.ObjectId, ref: 'Bar' }, name: String }); // Foo model, which contains denormalized bar data (just the name) const FooSchema = new Schema({ something: String, other: Number, bar: { type: BarNameSchema, ref: 'Bar' } }); const Foo = db.model('Foo', FooSchema); const bar = await Bar.create({ name: 'I am Bar', more: 'With more data', another: 2 }); const foo = await Foo.create({ something: 'I am Foo', other: 1, bar }); assert.ok(foo.bar instanceof Bar); assert.equal(foo.bar.another, 2); assert.equal(foo.get('bar.another'), 2); }); it('populating subdocument refs underneath maps throws (gh-12494) (gh-10856)', async function() { // Bar model, has a name property and some other properties that we are interested in const BarSchema = new Schema({ name: String, more: String, another: Number }); const Bar = db.model('Bar', BarSchema); // Denormalised Bar schema with just the name, for use on the Foo model const BarNameSchema = new Schema({ _id: { type: Schema.Types.ObjectId, ref: 'Bar' }, name: String }); // Foo model, which contains denormalized bar data (just the name) const FooSchema = new Schema({ something: String, other: Number, map: { type: Map, of: { type: BarNameSchema, ref: 'Bar' } } }); const Foo = db.model('Foo', FooSchema); const bar = await Bar.create({ name: 'I am Bar', more: 'With more data', another: 2 }); const { _id } = await Foo.create({ something: 'I am Foo', other: 1, map: { test: bar } }); const err = await Foo.findById(_id).populate('map').then(() => null, err => err); assert.ok(err); assert.ok(err.message.includes('Cannot manually populate single nested subdoc underneath Map'), err.message); }); it('handles save with undefined nested doc under subdoc (gh-11110)', async function() { const testSchema = new Schema({ level_1_array: [new Schema({ level_1: { level_2: new Schema({ level_3: { name_3: String, level_4: { name_4: String } } }) } })] }); const Test = db.model('Test', testSchema); const doc = { level_1_array: [{ level_1: { level_2: { level_3: { name_3: 'test', level_4: undefined } } } }] }; await new Test(doc).save(); }); it('correctly handles modifying array subdoc after setting array subdoc to same value (gh-11172)', async function() { const Order = db.model('Order', new Schema({ cumulativeConsumption: [{ _id: false, unit: String, value: Number }] })); await Order.create({ cumulativeConsumption: [{ unit: 'foo', value: 123 }, { unit: 'bar', value: 42 }] }); const doc = await Order.findOne(); doc.cumulativeConsumption = doc.toObject().cumulativeConsumption; const match = doc.cumulativeConsumption.find(o => o.unit === 'bar'); match.value = 43; match.unit = 'baz'; assert.ok(doc.isModified()); assert.ok(doc.isModified('cumulativeConsumption.1')); }); it('handles `String` with `type` (gh-11199)', function() { String.type = String; const schema = new mongoose.Schema({ something: String, somethingElse: { type: String, trim: true } }); const Test = db.model('Test', schema); const doc = new Test({ something: 'test', somethingElse: 'test 2' }); assert.equal(typeof doc.something, 'string'); assert.equal(typeof doc.somethingElse, 'string'); delete String.type; }); it('applies subdocument defaults when projecting dotted subdocument fields', async function() { const version = await start.mongodVersion(); if (version[0] < 5) { return this.skip(); } const grandChildSchema = new mongoose.Schema({ name: { type: mongoose.Schema.Types.String, default: () => 'grandchild' } }); const childSchema = new mongoose.Schema({ name: { type: mongoose.Schema.Types.String, default: () => 'child' }, grandChild: { type: grandChildSchema, default: () => ({}) } }); const parentSchema = new mongoose.Schema({ name: mongoose.Schema.Types.String, child: { type: childSchema, default: () => ({}) } }); const ParentModel = db.model('Parent', parentSchema); // insert an object without mongoose adding missing defaults const result = await db.collection('Parent').insertOne({ name: 'parent' }); // ensure that the defaults are populated when no projections are used const doc = await ParentModel.findById(result.insertedId).exec(); assert.equal(doc.name, 'parent'); assert.equal(doc.child.name, 'child'); assert.equal(doc.child.grandChild.name, 'grandchild'); // ensure that defaults are populated when using an object projection const projectedDoc = await ParentModel.findById(result.insertedId, { name: 1, child: { name: 1, grandChild: { name: 1 } } }).exec(); assert.equal(projectedDoc.name, 'parent'); assert.equal(projectedDoc.child.name, 'child'); assert.equal(projectedDoc.child.grandChild.name, 'grandchild'); // ensure that defaults are populated when using dotted path projections const dottedProjectedDoc = await ParentModel.findById(result.insertedId, { name: 1, 'child.name': 1, 'child.grandChild.name': 1 }).exec(); assert.equal(dottedProjectedDoc.name, 'parent'); assert.equal(dottedProjectedDoc.child.name, 'child'); assert.equal(dottedProjectedDoc.child.grandChild.name, 'grandchild'); }); it('handles initing nested properties in non-strict documents (gh-11309)', async function() { const NestedSchema = new Schema({}, { id: false, _id: false, strict: false }); const ItemSchema = new Schema({ name: { type: String }, nested: NestedSchema }); const Test = db.model('Test', ItemSchema); const item = await Test.create({ nested: { foo: { bar: 55 } } }); // Modify nested data item.nested.foo.bar = 66; item.markModified('nested.foo.bar'); await item.save(); const reloaded = await Test.findOne({ _id: item._id }); assert.deepEqual(reloaded.nested.foo, { bar: 66 }); assert.ok(!reloaded.nested.foo.$__isNested); assert.strictEqual(reloaded.nested.foo.bar, 66); }); it('saves changes when setting a nested path to itself (gh-11395)', async function() { const Test = db.model('Test', new Schema({ co: { value: Number } })); await Test.create({}); const doc = await Test.findOne(); doc.co.value = 123; doc.co = doc.co; await doc.save(); const res = await Test.findById(doc._id); assert.strictEqual(res.co.value, 123); }); it('avoids setting nested properties on top-level document when init-ing with strict: false (gh-11526) (gh-11309)', async function() { const testSchema = Schema({ name: String }, { strict: false, strictQuery: false }); const Test = db.model('Test', testSchema); const doc = new Test(); doc.init({ details: { person: { name: 'Baz' } } }); assert.strictEqual(doc.name, void 0); }); it('handles deeply nested subdocuments when getting paths to validate (gh-11501)', async function() { const schema = Schema({ parameters: { test: { type: new Schema({ value: 'Mixed' }) } }, nested: Schema({ parameters: { type: Map, of: Schema({ value: 'Mixed' }) } }) }); const Test = db.model('Test', schema); await Test.create({ nested: { parameters: new Map([['test', { answer: 42 }]]) } }); }); it('handles casting array of spread documents (gh-11522)', async function() { const Test = db.model('Test', new Schema({ arr: [{ _id: false, prop1: String, prop2: String }] })); const doc = new Test({ arr: [{ prop1: 'test' }] }); doc.arr = doc.arr.map(member => ({ ...member, prop2: 'foo' })); assert.deepStrictEqual(doc.toObject().arr, [{ prop1: 'test', prop2: 'foo' }]); await doc.validate(); }); it('avoids setting modified on subdocument defaults (gh-11528)', async function() { const textSchema = new Schema({ text: { type: String } }, { _id: false }); const messageSchema = new Schema({ body: { type: textSchema, default: { text: 'hello' } }, date: { type: Date, default: Date.now } });
const Message = db.model('Test', messageSchema); const entry = await Message.create({}); const failure = await Message.findById({ _id: entry._id }); assert.deepEqual(failure.modifiedPaths(), []); }); it('works when passing dot notation to mixed property (gh-1946)', async function() { const schema = Schema({ name: String, mix: { type: Schema.Types.Mixed }, nested: { prop: String } }); const M = db.model('Test', schema); const m1 = new M({ name: 'test', 'mix.val': 'foo', 'nested.prop': 'bar' }); assert.equal(m1.name, 'test'); assert.equal(m1.mix.val, 'foo'); assert.equal(m1.nested.prop, 'bar'); await m1.save(); assert.equal(m1.name, 'test'); assert.equal(m1.mix.val, 'foo'); const doc = await M.findById(m1); assert.equal(doc.name, 'test'); assert.equal(doc.mix.val, 'foo'); }); it('correctly validates deeply nested document arrays (gh-11564)', async function() { const testSchemaSub3 = new mongoose.Schema({ name: { type: String, required: true } }); const testSchemaSub2 = new mongoose.Schema({ name: { type: String, required: true }, list: [testSchemaSub3] }); const testSchemaSub1 = new mongoose.Schema({ name: { type: String, required: true }, list: [testSchemaSub2] }); const testSchema = new mongoose.Schema({ name: String, list: [testSchemaSub1] }); const testModel = db.model('Test', testSchema); await testModel.create({ name: 'lvl1', list: [{ name: 'lvl2', list: [{ name: 'lvl3' }] }] }); }); it('reruns validation when modifying a document array path under a nested path after save (gh-11672)', async function() { const ChildSchema = new Schema({ price: { type: Number, validate: function(val) { return val > 0; } } }); const ParentSchema = new Schema({ rootField: { nestedSubdocArray: [ChildSchema] } }); const Test = db.model('Test', ParentSchema); const parentDoc = new Test({ rootField: { nestedSubdocArray: [ { price: 1 } ] } }); await parentDoc.save(); // Now we try editing to an invalid value which should throw parentDoc.rootField.nestedSubdocArray[0].price = -1; const err = await parentDoc.save().then(() => null, err => err); assert.ok(err); assert.equal(err.name, 'ValidationError'); assert.ok(err.message.includes('failed for path'), err.message); assert.ok(err.message.includes('value `-1`'), err.message); }); it('avoids setting nested paths to null when they are set to `undefined` (gh-11723)', async function() { const nestedSchema = new mongoose.Schema({ count: Number }, { _id: false }); const mySchema = new mongoose.Schema({ name: String, nested: { count: Number }, nestedSchema: nestedSchema }, { minimize: false }); const Test = db.model('Test', mySchema); const instance1 = new Test({ name: 'test1', nested: { count: 1 }, nestedSchema: { count: 1 } }); await instance1.save(); const update = { nested: { count: undefined }, nestedSchema: { count: undefined } }; instance1.set(update); await instance1.save(); const doc = await Test.findById(instance1); assert.strictEqual(doc.nested.count, undefined); assert.strictEqual(doc.nestedSchema.count, undefined); }); it('cleans modified subpaths when setting nested path under array to null when subpaths are modified (gh-11764)', async function() { const Test = db.model('Test', new Schema({ list: [{ quantity: { value: Number, unit: String } }] })); let doc = await Test.create({ list: [{ quantity: { value: 1, unit: 'case' } }] }); doc = await Test.findById(doc); doc.list[0].quantity.value = null; doc.list[0].quantity.unit = null; doc.list[0].quantity = null; await doc.save(); doc = await Test.findById(doc); assert.strictEqual(doc.list[0].toObject().quantity, null); }); it('avoids manually populating document that is manually populated in another doc with different unpopulatedValue (gh-11442) (gh-11008)', async function() { const BarSchema = new Schema({ name: String, more: String }); const Bar = db.model('Bar', BarSchema); // Denormalised Bar schema with just the name, for use on the Foo model const BarNameSchema = new Schema({ _id: { type: Schema.Types.ObjectId, ref: 'Bar' }, name: String }); // Foo model, which contains denormalized bar data (just the name) const FooSchema = new Schema({ something: String, other: Number, bar: { type: BarNameSchema, ref: 'Bar' } }); const Foo = db.model('Foo', FooSchema); const Baz = db.model('Baz', new Schema({ bar: { type: 'ObjectId', ref: 'Bar' } })); const bar = await Bar.create({ name: 'I am another Bar', more: 'With even more data' }); const foo = await Foo.create({ something: 'I am another Foo', other: 4 }); foo.bar = bar; const baz = await Baz.create({}); baz.bar = bar; assert.ok(foo.populated('bar')); assert.ok(!baz.populated('bar')); let res = foo.toObject({ depopulate: true }); assert.strictEqual(res.bar._id.toString(), bar._id.toString()); assert.strictEqual(res.bar.name, 'I am another Bar'); res = baz.toObject({ depopulate: true }); assert.strictEqual(res.bar.toString(), bar._id.toString()); const bar2 = await Bar.create({ name: 'test2' }); baz.bar = bar2; assert.ok(baz.populated('bar')); const baz2 = await Baz.create({}); baz2.bar = bar2; assert.ok(baz.populated('bar')); }); it('$getAllSubdocs gets document arrays underneath a nested path (gh-11917)', function() { const nestedSettingsSchema = new Schema({ value: String, active: Boolean }); const userSettingsSchema = new Schema({ nestedSettings: { settingsProps: [nestedSettingsSchema] } }); const userSchema = new Schema({ first_name: String, last_name: String, settings: userSettingsSchema }); const User = db.model('User', userSchema); const doc = new User({ settings: { nestedSettings: { settingsProps: [{ value: 'test', active: true }] } } }); const subdocs = doc.$getAllSubdocs(); assert.equal(subdocs.length, 2); assert.equal(subdocs[0].value, 'test'); assert.ok(subdocs[1].nestedSettings); }); it('handles validation errors on deeply nested subdocuments underneath a nested path (gh-12021)', async function() { const SubSubSchema = new mongoose.Schema( { from: { type: mongoose.Schema.Types.String, required: true } }, { _id: false } ); const SubSchema = new mongoose.Schema( { nested: { type: SubSubSchema, required: false // <-- important } }, { _id: false } ); const TestLeafSchema = new mongoose.Schema({ testProp: { testSubProp: { type: SubSchema, required: true } } }); const TestLeafModel = mongoose.model('test-leaf-model', TestLeafSchema); const testModelInstance = new TestLeafModel({ testProp: { testSubProp: { nested: { from: null } } } }); const err = await testModelInstance.validate().then(() => null, err => err); assert.ok(err); assert.ok(err.errors['testProp.testSubProp.nested.from']); }); describe('$inc (gh-11915)', function() { describe('top-level path', function() { let Test; beforeEach(function() { const schema = new Schema({ counter: Number }); Test = db.model('Test', schema); }); it('sends a $inc command for a given path', async function() { await Test.create({ counter: 0 }); const doc = await Test.findOne(); assert.strictEqual(doc.counter, 0); const doc2 = await Test.findOne(); doc2.counter = 1; await doc2.save(); doc.$inc('counter', 1); await doc.save(); const res = await Test.findById(doc); assert.equal(res.counter, 2); }); it('works as a $set if the document is new', async function() { const doc = new Test({ counter: 0 }); doc.$inc('counter', 2); assert.equal(doc.counter, 2); await doc.save(); const res = await Test.findById(doc); assert.equal(res.counter, 2); }); it('treats as a $set if set after $inc', async function() { await Test.create({ counter: 0 }); const doc = await Test.findOne(); doc.$inc('counter', 2); doc.counter = 5; assert.deepStrictEqual(doc.getChanges(), { $set: { counter: 5 } }); await doc.save(); const res = await Test.findOne(); assert.equal(res.counter, 5); }); it('tries to cast to number', async function() { await Test.create({ counter: 0 }); const doc = await Test.findOne(); doc.$inc('counter', '2'); assert.deepStrictEqual(doc.getChanges(), { $inc: { counter: 2 } }); await doc.save(); const res = await Test.findOne(); assert.equal(res.counter, 2); }); it('stores CastError if can\'t convert to number', async function() { await Test.create({ counter: 0 }); const doc = await Test.findOne(); doc.$inc('counter', 'foobar'); const err = await doc.save().then(() => null, err => err); assert.ok(err); assert.equal(err.errors['counter'].name, 'CastError'); }); }); describe('nested paths', function() { let Test; beforeEach(function() { const schema = new Schema({ nested: { counter: Number } }); Test = db.model('Test', schema); }); it('handles nested paths', async function() { await Test.create({ nested: { counter: 0 } }); const doc = await Test.findOne(); doc.$inc('nested.counter', 2); await doc.save(); const res = await Test.findById(doc); assert.equal(res.nested.counter, 2); }); it('treats as $set if overwriting nested path', async function() { await Test.create({ nested: { counter: 0 } }); const doc = await Test.findOne(); doc.$inc('nested.counter', 2); doc.nested.counter += 3; await doc.save(); const res = await Test.findById(doc); assert.equal(res.nested.counter, 5); }); }); describe('subdocuments', function() { let Test; beforeEach(function() { const schema = new Schema({ subdoc: new Schema({ counter: Number }) }); Test = db.model('Test', schema); }); it('handles paths underneath subdocuments', async function() { await Test.create({ subdoc: { counter: 0 } }); const doc = await Test.findOne(); doc.$inc('subdoc.counter', 2); await doc.save(); const res = await Test.findById(doc); assert.equal(res.subdoc.counter, 2); }); it('treats as a $set if setting subdocument after $inc', async function() { await Test.create({ subdoc: { counter: 0 } }); const doc = await Test.findOne(); doc.$inc('subdoc.counter', 2); doc.subdoc = { counter: 5 }; await doc.save(); const res = await Test.findById(doc); assert.equal(res.subdoc.counter, 5); }); }); describe('document array', function() { let Test; beforeEach(function() { const schema = new Schema({ docArr: [{ counter: Number }] }); Test = db.model('Test', schema); }); it('handles paths underneath subdocuments', async function() { await Test.create({ docArr: [{ counter: 0 }] }); const doc = await Test.findOne(); doc.docArr[0].$inc('counter'); await doc.save(); const res = await Test.findById(doc); assert.equal(res.docArr[0].counter, 1); }); it('works on pushed subdocs', async function() { await Test.create({ docArr: [] }); const doc = await Test.findOne(); doc.docArr.push({ counter: 0 }); doc.docArr[0].$inc('counter'); await doc.save(); const res = await Test.findById(doc); assert.equal(res.docArr[0].counter, 1); }); it('Splice call registers path modification', async function() { await Test.create({ docArr: [{ counter: 0 }, { counter: 2 }, { counter: 3 }, { counter: 4 }] }); const doc = await Test.findOne(); doc.docArr.splice(1, 0, { counter: 1 }); assert.equal(doc.isModified('docArr'), true); }); }); it('stores CastError if trying to $inc a non-numeric path', async function() { const schema = new Schema({ prop: String }); const Test = db.model('Test', schema); await Test.create({ prop: '' }); const doc = await Test.findOne(); doc.$inc('prop', 2); const err = await doc.save().then(() => null, err => err); assert.ok(err); assert.equal(err.errors['prop'].name, 'CastError'); }); }); it('supports virtuals named `isValid` (gh-12124) (gh-6262)', async function() { const Schema = new mongoose.Schema({ test: String, data: { sub: String } }); Schema.virtual('isValid'); const Test = db.model('Test', Schema); let doc = new Test(); assert.ok(doc.$isValid('test')); await doc.save(); doc = await Test.findOne(); doc.set('isValid', true); assert.ok(doc.$isValid('test')); doc.set({ test: 'test' }); await doc.save(); assert.equal(doc.test, 'test'); doc.set({ data: { sub: 'sub' } }); await doc.save(); assert.equal(doc.data.sub, 'sub'); }); it('handles maps when applying defaults to nested paths (gh-12220)', async function() { const nestedSchema = new mongoose.Schema({ 1: { type: Number, default: 0 } }); const topSchema = new mongoose.Schema({ nestedPath1: { mapOfSchema: { type: Map, of: nestedSchema } } }); const Test = db.model('Test', topSchema); const data = { nestedPath1: { mapOfSchema: {} } }; const doc = await Test.create(data); assert.ok(doc.nestedPath1.mapOfSchema); }); it('correct context for default functions in subdocuments with init (gh-12328)', async function() { let called = 0; const subSchema = new mongoose.Schema({ propertyA: { type: String }, propertyB: { type: String, default: function() { ++called; return this.propertyA; } } }); const testSchema = new mongoose.Schema( { name: String, sub: { type: subSchema, default: () => ({}) } } ); const Test = db.model('Test', testSchema); await Test.collection.insertOne({ name: 'test', sub: { propertyA: 'foo' } }); assert.strictEqual(called, 0); const doc = await Test.findOne({ name: 'test' }); assert.strictEqual(doc.sub.propertyB, 'foo'); assert.strictEqual(called, 1); }); it('applies defaults to pushed subdocs after initing document (gh-12515)', async function() { const animalSchema = new Schema({ title: String }); const animalsSchema = new Schema({ species: [animalSchema], totalAnimals: Number }); const Userschema = new Schema({ animals: animalsSchema }); const UserModel = db.model('User', Userschema); const doc = new UserModel(); doc.animals = { totalAnimals: 1 }; doc.animals.species = [{ title: 'Lion' }]; await doc.save(); // once created we fetch it again let user = await UserModel.findById(doc._id); // add new animal user.animals.species.push({ title: 'Elephant' }); await user.save(); assert.ok(user.animals.species[0]._id); assert.ok(user.animals.species[1]._id); user = await UserModel.collection.findOne({ _id: user._id }); assert.ok(user.animals.species[0]._id); assert.ok(user.animals.species[1]._id); }); it('If the field does not exist, $inc should create it and set is value to the specified one (gh-12435)', async function() { const schema = new mongoose.Schema({ name: String, count: Number }); const Model = db.model('IncTest', schema); const doc = new Model({ name: 'Test' }); await doc.save(); doc.$inc('count', 1); await doc.save(); assert.strictEqual(doc.count, 1); const addedDoc = await Model.findOne({ name: 'Test' }); assert.strictEqual(addedDoc.count, 1); }); it('avoids overwriting array if saving with no changes with array deselected (gh-12414)', async function() { const schema = new mongoose.Schema({ name: String, tags: [String] }); const Test = db.model('Test', schema); const { _id } = await Test.create({ name: 'Mongoose', tags: ['mongodb'] }); const doc = await Test.findById(_id).select('name'); assert.deepStrictEqual(doc.getChanges(), {}); await doc.save(); const rawDoc = await Test.collection.findOne({ _id }); assert.ok(rawDoc); assert.deepStrictEqual(rawDoc.tags, ['mongodb']); }); it('can create document with document array and top-level key named `schema` (gh-12480)', async function() { const AuthorSchema = new Schema({ fullName: { type: 'String', required: true } }); const BookSchema = new Schema({ schema: { type: 'String', required: true }, title: { type: 'String', required: true }, authors: [AuthorSchema] }, { supressReservedKeysWarning: true }); const Book = db.model('Book', BookSchema); await Book.create({ schema: 'design', authors: [{ fullName: 'Sourabh Bagrecha' }], title: 'The power of JavaScript' }); }); it('handles setting array to itself after saving and pushing a new value (gh-12656)', async function() { const Test = db.model('Test', new Schema({ list: [{ a: Number }] })); await Test.create({ list: [{ a: 1, b: 11 }] }); let doc = await Test.findOne(); doc.list.push({ a: 2 }); doc.list = [...doc.list]; await doc.save(); doc.list.push({ a: 3 }); doc.list = [...doc.list]; await doc.save(); doc = await Test.findOne(); assert.equal(doc.list.length, 3); assert.deepStrictEqual(doc.list.map(el => el.a), [1, 2, 3]); });});
describe('Check if instance function that is supplied in schema option is availabe', function() { it('should give an instance function back rather than undefined', function ModelJS() { const testSchema = new mongoose.Schema({}, { methods: { instanceFn() { return 'Returned from DocumentInstanceFn'; } } }); const TestModel = mongoose.model('TestModel', testSchema); const TestDocument = new TestModel({}); assert.equal(TestDocument.instanceFn(), 'Returned from DocumentInstanceFn'); });});
Version Info