tests.py 181 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2013-2015 The Distro Tracker Developers
  3. # See the COPYRIGHT file at the top-level directory of this distribution and
  4. # at http://deb.li/DTAuthors
  5. #
  6. # This file is part of Distro Tracker. It is subject to the license terms
  7. # in the LICENSE file found in the top-level directory of this
  8. # distribution and at http://deb.li/DTLicense. No part of Distro Tracker,
  9. # including this file, may be copied, modified, propagated, or distributed
  10. # except according to the terms contained in the LICENSE file.
  11. """
  12. Tests for Debian-specific modules/functionality of Distro Tracker.
  13. """
  14. from __future__ import unicode_literals
  15. from distro_tracker.test import TestCase, SimpleTestCase
  16. from django.test.utils import override_settings
  17. from django.core import mail
  18. from django.core.urlresolvers import reverse
  19. from django.core.management import call_command
  20. from django.utils import six
  21. from django.utils.six.moves import mock
  22. from django.utils.encoding import force_bytes
  23. from django.utils.functional import curry
  24. from distro_tracker.mail.tests.tests_dispatch \
  25. import DispatchTestHelperMixin
  26. from distro_tracker.accounts.models import User
  27. from distro_tracker.accounts.models import UserEmail
  28. from distro_tracker.test.utils import make_temp_directory
  29. from distro_tracker.test.utils import set_mock_response
  30. from distro_tracker.core.utils.email_messages import message_from_bytes
  31. from distro_tracker.core.models import ActionItem, ActionItemType
  32. from distro_tracker.core.models import News
  33. from distro_tracker.core.models import Keyword
  34. from distro_tracker.core.models import EmailSettings
  35. from distro_tracker.core.models import Subscription
  36. from distro_tracker.core.models import PackageExtractedInfo
  37. from distro_tracker.core.models import PackageName
  38. from distro_tracker.core.models import SourcePackage
  39. from distro_tracker.core.models import PseudoPackageName
  40. from distro_tracker.core.models import SourcePackageName
  41. from distro_tracker.core.models import Repository
  42. from distro_tracker.core.tasks import run_task
  43. from distro_tracker.core.retrieve_data import UpdateRepositoriesTask
  44. from distro_tracker.vendor.debian.rules import get_package_information_site_url
  45. from distro_tracker.vendor.debian.rules import get_maintainer_extra
  46. from distro_tracker.vendor.debian.rules import get_uploader_extra
  47. from distro_tracker.vendor.debian.rules import get_developer_information_url
  48. from distro_tracker.vendor.debian.rules import classify_message
  49. from distro_tracker.vendor.debian.tracker_tasks import UpdateNewQueuePackages
  50. from distro_tracker.vendor.debian.tracker_tasks import UpdateWnppStatsTask
  51. from distro_tracker.vendor.debian.tracker_tasks import UpdateUbuntuStatsTask
  52. from distro_tracker.vendor.debian.tracker_tasks import UpdateSecurityIssuesTask
  53. from distro_tracker.vendor.debian.tracker_tasks import UpdatePiuPartsTask
  54. from distro_tracker.vendor.debian.tracker_tasks import UpdateBuildLogCheckStats
  55. from distro_tracker.vendor.debian.tracker_tasks import UpdatePackageBugStats
  56. from distro_tracker.vendor.debian.tracker_tasks \
  57. import RetrieveDebianMaintainersTask
  58. from distro_tracker.vendor.debian.tracker_tasks \
  59. import RetrieveLowThresholdNmuTask
  60. from distro_tracker.vendor.debian.tracker_tasks \
  61. import DebianWatchFileScannerUpdate
  62. from distro_tracker.vendor.debian.tracker_tasks import UpdateExcusesTask
  63. from distro_tracker.vendor.debian.tracker_tasks import UpdateDebciStatusTask
  64. from distro_tracker.vendor.debian.tracker_tasks import UpdateDebianDuckTask
  65. from distro_tracker.vendor.debian.tracker_tasks \
  66. import UpdateAutoRemovalsStatsTask
  67. from distro_tracker.vendor.debian.tracker_tasks \
  68. import UpdatePackageScreenshotsTask
  69. from distro_tracker.vendor.debian.tracker_tasks \
  70. import UpdateBuildReproducibilityTask
  71. from distro_tracker.vendor.debian.models import DebianContributor
  72. from distro_tracker.vendor.debian.models import UbuntuPackage
  73. from distro_tracker.vendor.debian.tracker_tasks import UpdateLintianStatsTask
  74. from distro_tracker.vendor.debian.models import LintianStats
  75. from distro_tracker.vendor.debian.management.commands\
  76. .tracker_import_old_subscriber_dump \
  77. import Command as ImportOldSubscribersCommand
  78. from distro_tracker.vendor.debian.management.commands\
  79. .tracker_import_old_tags_dump \
  80. import Command as ImportOldTagsCommand
  81. from distro_tracker.vendor.debian.sso_auth import DebianSsoUserBackend
  82. from distro_tracker.vendor.debian.views import CodeSearchView
  83. from distro_tracker.mail.mail_news import process
  84. from email.message import Message
  85. from bs4 import BeautifulSoup as soup
  86. import os
  87. import json
  88. import logging
  89. logging.disable(logging.CRITICAL)
  90. @override_settings(
  91. DISTRO_TRACKER_VENDOR_RULES='distro_tracker.vendor.debian.rules')
  92. class DispatchDebianSpecificTest(TestCase, DispatchTestHelperMixin):
  93. """
  94. Tests Debian-specific keyword classification.
  95. """
  96. def setUp(self):
  97. self.clear_message()
  98. self.from_email = 'dummy-email@domain.com'
  99. self.set_package_name('dummy-package')
  100. self.add_header('From', 'Real Name <{from_email}>'.format(
  101. from_email=self.from_email))
  102. self.add_header('Subject', 'Some subject')
  103. self.set_message_content('message content')
  104. self.package = PackageName.objects.create(
  105. source=True,
  106. name=self.package_name)
  107. def test_default_not_trusted(self):
  108. """
  109. Tests that a non-trusted default message is dropped.
  110. """
  111. self.subscribe_user_to_package('user@domain.com', self.package_name)
  112. self.run_dispatch()
  113. self.assertEqual(len(mail.outbox), 0)
  114. def test_debian_trusts_bugzilla(self):
  115. """
  116. Tests that messages tagged with the default keyword are forwarded when
  117. they originated from Bugzilla.
  118. """
  119. self.set_header('X-Bugzilla-Product', '1')
  120. self.subscribe_user_to_package('user@domain.com', self.package_name)
  121. self.run_dispatch()
  122. self.assertEqual(len(mail.outbox), 1)
  123. def test_debian_specific_headers(self):
  124. """
  125. Tests that debian specific headers are included in forwarded messages.
  126. """
  127. expected_headers = [
  128. ('X-Debian-Package', self.package_name),
  129. ('X-Debian', 'tracker.debian.org'),
  130. ]
  131. self.subscribe_user_to_package('user@domain.com', self.package_name)
  132. self.run_dispatch()
  133. self.assert_all_headers_found(expected_headers)
  134. def run_classify(self, package=None, keyword=None):
  135. return classify_message(self.message, package, keyword)
  136. def _test_classify_converts_legacy_keyword(self, keyword, expected):
  137. package, new_keyword = self.run_classify('foo', keyword)
  138. self.assertEqual(new_keyword, expected)
  139. def test_classify_converts_legacy_keyword(self):
  140. conversions = {
  141. 'cvs': 'vcs',
  142. 'ddtp': 'translation',
  143. 'buildd': 'build',
  144. 'katie-other': 'archive',
  145. }
  146. for old, new in conversions.items():
  147. self._test_classify_converts_legacy_keyword(old, new)
  148. def define_bts_mail(self, package, message='report 12345', source=None):
  149. self.set_header('X-Loop', 'owner@bugs.debian.org')
  150. self.set_header('X-Debian-PR-Message', message)
  151. self.set_header('X-Debian-PR-Package', package)
  152. if source:
  153. self.set_header('X-Debian-PR-Source', source)
  154. def test_classify_bts_mail_traffic_of_normal_package(self):
  155. self.define_bts_mail('pkg-binary', source='pkg-source')
  156. pkg, _ = self.run_classify()
  157. self.assertEqual(pkg, 'pkg-source')
  158. def test_classify_bts_mail_traffic_of_pseudo_package(self):
  159. self.define_bts_mail('pkg-pseudo', source=None)
  160. pkg, _ = self.run_classify()
  161. self.assertEqual(pkg, 'pkg-pseudo')
  162. def test_classify_bts_mail_traffic_with_correct_keyword(self):
  163. self.define_bts_mail('foo', message='followup 12345')
  164. pkg, keyword = self.run_classify()
  165. self.assertEqual(keyword, 'bts')
  166. def test_classify_bts_control_traffic_with_correct_keyword(self):
  167. self.define_bts_mail('foo', message='transcript')
  168. pkg, keyword = self.run_classify()
  169. self.assertEqual(keyword, 'bts-control')
  170. def test_classify_bts_mail_on_multiple_packages_with_suggestion(self):
  171. """
  172. Suggested package takes precedence when the mail header
  173. mentions multiple packages.
  174. """
  175. self.define_bts_mail('pkg-binary', source='a b c d')
  176. pkg, keyword = self.run_classify('pkg-source')
  177. self.assertEqual(pkg, 'pkg-source')
  178. def test_classify_bts_mail_on_multiple_packages_without_suggestion(self):
  179. """
  180. Since we have no suggested package, we assume all packages need
  181. to be informed and we return a list
  182. """
  183. self.define_bts_mail('pkg-binary', source=' a b c d ')
  184. pkg, keyword = self.run_classify()
  185. self.assertListEqual(pkg, ['a', 'b', 'c', 'd'])
  186. def test_classify_bts_mail_does_not_override_suggestion(self):
  187. """
  188. This case ensures that we can send a X-Debbugs-Cc copy of a bug report
  189. to another maintainer via pkg-foo@packages.debian.org and still get the
  190. bug forwarded to the pkg-foo subscribers under the contact keyword.
  191. """
  192. self.define_bts_mail('release.debian.org', source=None)
  193. pkg, keyword = self.run_classify('pkg-foo', 'contact')
  194. self.assertEqual(pkg, 'pkg-foo')
  195. self.assertEqual(keyword, 'contact')
  196. def define_dak_mail(self, package='foo', subject=None,
  197. dak_cmd='dak process-upload'):
  198. self.set_header('X-DAK', dak_cmd)
  199. self.set_header('X-Debian', 'DAK')
  200. if package:
  201. self.set_header('X-Debian-Package', package)
  202. if subject:
  203. self.set_header('Subject', subject)
  204. def test_classify_identifies_package_in_dak_mails(self):
  205. self.define_dak_mail(package='pkg-a')
  206. pkg, _ = self.run_classify()
  207. self.assertEqual(pkg, 'pkg-a')
  208. def test_classify_binary_upload_mails(self):
  209. subject = 'foo_1.0-1_amd64.changes ACCEPTED into unstable'
  210. self.define_dak_mail(subject=subject)
  211. _, keyword = self.run_classify()
  212. self.assertEqual(keyword, 'upload-binary')
  213. def test_classify_source_upload_mails(self):
  214. subject = 'foo_1.0-1_amd64.changes ACCEPTED into unstable'
  215. self.define_dak_mail(subject=subject)
  216. self.set_message_content('' + 'a' * 40 + ' 1234 foo_1.0-1.dsc\n')
  217. _, keyword = self.run_classify()
  218. self.assertEqual(keyword, 'upload-source')
  219. def test_classify_other_archive_mails(self):
  220. subject = 'Comments regarding foo_1.0-1_amd64.changes'
  221. self.define_dak_mail(subject=subject)
  222. _, keyword = self.run_classify()
  223. self.assertEqual(keyword, 'archive')
  224. @mock.patch('distro_tracker.mail.mail_news.create_news')
  225. def test_classify_stores_dak_source_accepted_as_news(self,
  226. mock_create_news):
  227. subject = 'Accepted libosmium 2.5.3-1~exp2 (source) into experimental'
  228. self.define_dak_mail(package='pkg-a', subject=subject)
  229. self.run_classify()
  230. mock_create_news.assert_called_with(self.message, 'pkg-a',
  231. create_package=True)
  232. def test_classify_creates_package_name_on_first_accepted_mail(self):
  233. subject = 'Accepted libosmium 2.5.3-1~exp2 (source) into experimental'
  234. self.define_dak_mail(package='pkg-a', subject=subject)
  235. self.run_classify()
  236. self.assertIsNotNone(PackageName.objects.get(name='pkg-a'))
  237. @mock.patch('distro_tracker.mail.mail_news.create_news')
  238. def test_classify_does_not_store_dak_binary_accepted_as_news(
  239. self, mock_create_news):
  240. subject = 'Accepted libosmium 2.5.3-1~exp2 (i386 all) into experimental'
  241. self.define_dak_mail(package='pkg-a', subject=subject)
  242. self.run_classify()
  243. self.assertFalse(mock_create_news.called)
  244. def define_dak_rm_mail(self, **kwargs):
  245. subject = 'Bug#123: Removed package(s) from unstable'
  246. packages = kwargs.pop('packages', [self.package_name])
  247. self.define_dak_mail(dak_cmd='dak rm', subject=subject, package=None,
  248. **kwargs)
  249. content = (
  250. 'We believe that the bug you reported is now fixed; the following\n'
  251. 'package(s) have been removed from unstable:\n\n'
  252. )
  253. for pkg in packages:
  254. content += '{pkg} | 1.2-1 | source, amd64\n'.format(pkg=pkg)
  255. self.set_message_content(content)
  256. def test_classify_dak_rm_mail(self):
  257. self.define_dak_rm_mail(packages=['pkg-a'])
  258. pkg, keyword = self.run_classify()
  259. self.assertEqual(pkg, 'pkg-a')
  260. self.assertEqual(keyword, 'archive')
  261. def test_classify_dak_rm_mail_multiple_sources(self):
  262. self.define_dak_rm_mail(packages=['pkg-a', 'pkg-b'])
  263. pkg, keyword = self.run_classify()
  264. self.assertEqual(pkg, ['pkg-a', 'pkg-b'])
  265. self.assertEqual(keyword, 'archive')
  266. def test_classify_generates_news_with_dak_rm_mail(self):
  267. self.define_dak_rm_mail()
  268. self.assertEqual(self.package.news_set.count(), 0)
  269. pkg, keyword = self.run_classify()
  270. self.assertEqual(self.package.news_set.count(), 1)
  271. def test_classify_testing_watch_mail(self):
  272. self.add_header('X-Testing-Watch-Package', 'pkg-a')
  273. pkg, keyword = self.run_classify()
  274. self.assertEqual(pkg, 'pkg-a')
  275. self.assertEqual(keyword, 'summary')
  276. def test_classify_generates_news_with_testing_watch_mail(self):
  277. self.add_header('X-Testing-Watch-Package', self.package_name)
  278. self.assertEqual(self.package.news_set.count(), 0)
  279. pkg, keyword = self.run_classify()
  280. self.assertEqual(self.package.news_set.count(), 1)
  281. def test_classify_git_mail(self):
  282. self.add_header('X-Git-Repo', self.package_name)
  283. pkg, keyword = self.run_classify()
  284. self.assertEqual(pkg, self.package_name)
  285. self.assertEqual(keyword, 'vcs')
  286. def test_classify_git_mail_drops_git_suffix_from_repo_name(self):
  287. self.add_header('X-Git-Repo', self.package_name + '.git')
  288. pkg, keyword = self.run_classify()
  289. self.assertEqual(pkg, self.package_name)
  290. self.assertEqual(keyword, 'vcs')
  291. def test_classify_git_mail_keeps_basename_only(self):
  292. self.add_header('X-Git-Repo', 'packages/unstable/' + self.package_name)
  293. pkg, keyword = self.run_classify()
  294. self.assertEqual(pkg, self.package_name)
  295. self.assertEqual(keyword, 'vcs')
  296. class GetPseudoPackageListTest(TestCase):
  297. @mock.patch('distro_tracker.core.utils.http.requests')
  298. def test_debian_pseudo_packages(self, mock_requests):
  299. """
  300. Tests that Debian-specific function for retrieving allowed pseudo
  301. packages uses the correct source and properly parses it.
  302. """
  303. from distro_tracker.vendor.debian.rules import get_pseudo_package_list
  304. mock_response = mock_requests.models.Response()
  305. mock_response.status_code = 200
  306. mock_response.text = (
  307. 'package1 text here\n'
  308. 'package2\t\t text'
  309. )
  310. mock_response.content = mock_response.text.encode('utf-8')
  311. mock_response.ok = True
  312. mock_requests.get.return_value = mock_response
  313. packages = get_pseudo_package_list()
  314. # Correct URL used?
  315. mock_requests.get.assert_called_with(
  316. 'https://bugs.debian.org/pseudo-packages.maintainers',
  317. headers={},
  318. allow_redirects=True,
  319. verify=False)
  320. # Correct packages extracted?
  321. self.assertSequenceEqual(
  322. ['package1', 'package2'],
  323. packages
  324. )
  325. class GetPackageInformationSiteUrlTest(SimpleTestCase):
  326. def setUp(self):
  327. self.repository = {
  328. 'name': 'Debian Stable',
  329. 'suite': 'stable',
  330. 'codename': 'wheezy',
  331. 'shorthand': 'stable',
  332. }
  333. def test_get_source_package_url(self):
  334. """
  335. Tests retrieving a URL to the package information site for a source
  336. package.
  337. """
  338. # Source package with no repository given
  339. self.assertEqual(
  340. 'https://packages.debian.org/src:dpkg',
  341. get_package_information_site_url('dpkg', source_package=True)
  342. )
  343. # Source package in a repository
  344. self.assertEqual(
  345. 'https://packages.debian.org/source/stable/dpkg',
  346. get_package_information_site_url('dpkg', source_package=True,
  347. repository=self.repository)
  348. )
  349. # Source package in a proposed-updates repository
  350. url = 'https://release.debian.org/proposed-updates/{}.html#dpkg_1.6.15'
  351. for suite in ('stable', 'oldstable'):
  352. self.repository['suite'] = '{}-proposed-updates'.format(suite)
  353. self.assertEqual(
  354. url.format(suite),
  355. get_package_information_site_url('dpkg', source_package=True,
  356. repository=self.repository,
  357. version='1.6.15')
  358. )
  359. def test_get_binary_package_url(self):
  360. """
  361. Tests retrieving a URL to the package information site for a binary
  362. package.
  363. """
  364. # Binary package with no repository given
  365. self.assertEqual(
  366. 'https://packages.debian.org/dpkg',
  367. get_package_information_site_url('dpkg')
  368. )
  369. # Binary package in a repository
  370. self.assertEqual(
  371. 'https://packages.debian.org/stable/dpkg',
  372. get_package_information_site_url(
  373. 'dpkg',
  374. repository=self.repository))
  375. # Binary package in a proposed-updates repository
  376. for suite in ('stable', 'oldstable'):
  377. self.repository['suite'] = '{}-proposed-updates'.format(suite)
  378. self.assertEqual(
  379. '',
  380. get_package_information_site_url('dpkg', source_package=False,
  381. repository=self.repository,
  382. version='1.6.15')
  383. )
  384. class GetDeveloperInformationSiteUrlTest(SimpleTestCase):
  385. def test_get_developer_site_info_url(self):
  386. """
  387. Test retrieving a URL to a developer information Web site.
  388. """
  389. developer_email = 'debian-dpkg@lists.debian.org'
  390. self.assertEqual(
  391. 'https://qa.debian.org/developer.php'
  392. '?email=debian-dpkg%40lists.debian.org',
  393. get_developer_information_url(developer_email))
  394. developer_email = 'email@domain.com'
  395. self.assertEqual(
  396. 'https://qa.debian.org/developer.php?email=email%40domain.com',
  397. get_developer_information_url(developer_email)
  398. )
  399. class RetrieveLowThresholdNmuTest(TestCase):
  400. @mock.patch('distro_tracker.core.utils.http.requests')
  401. def test_developer_did_not_exist(self, mock_requests):
  402. """
  403. Tests updating the list of developers that allow the low threshold
  404. NMU when the developer did not previously exist in the database.
  405. """
  406. set_mock_response(mock_requests,
  407. "Text text text\n"
  408. "text more text...\n"
  409. " 1. [[DeveloperName|Name]] - "
  410. "([[https://qa.debian.org/developer.php?"
  411. "login=dummy|all packages]])\n")
  412. run_task(RetrieveLowThresholdNmuTask)
  413. # A Debian developer created
  414. self.assertEqual(DebianContributor.objects.count(), 1)
  415. d = DebianContributor.objects.all()[0]
  416. self.assertTrue(d.agree_with_low_threshold_nmu)
  417. @mock.patch('distro_tracker.core.utils.http.requests')
  418. def test_developer_existed(self, mock_requests):
  419. """
  420. Tests updating the list of developers that allow the low threshold
  421. NMU when the developer was previously registered in the database.
  422. """
  423. UserEmail.objects.create(email='dummy@debian.org')
  424. set_mock_response(mock_requests,
  425. "Text text text\n"
  426. "text more text...\n"
  427. " 1. [[DeveloperName|Name]] - "
  428. "([[https://qa.debian.org/developer.php?"
  429. "login=dummy|all packages]])\n")
  430. run_task(RetrieveLowThresholdNmuTask)
  431. # Still only one debian developer instance
  432. self.assertEqual(DebianContributor.objects.count(), 1)
  433. d = DebianContributor.objects.all()[0]
  434. self.assertTrue(d.agree_with_low_threshold_nmu)
  435. @mock.patch('distro_tracker.core.utils.http.requests')
  436. def test_developer_remove_nmu(self, mock_requests):
  437. """
  438. Tests updating the list of NMU developers when one of them needs to be
  439. removed from the list.
  440. """
  441. # Set up a Debian developer that is already in the NMU list.
  442. email = UserEmail.objects.create(email='dummy@debian.org')
  443. DebianContributor.objects.create(email=email,
  444. agree_with_low_threshold_nmu=True)
  445. set_mock_response(mock_requests,
  446. "Text text text\n"
  447. "text more text...\n"
  448. " 1. [[DeveloperName|Name]] - "
  449. "([[https://qa.debian.org/developer.php?"
  450. "login=other|all packages]])\n")
  451. run_task(RetrieveLowThresholdNmuTask)
  452. d = DebianContributor.objects.get(email__email='dummy@debian.org')
  453. # The Debian developer is no longer in the list of low threshold nmu
  454. self.assertFalse(d.agree_with_low_threshold_nmu)
  455. class RetrieveDebianMaintainersTest(TestCase):
  456. @mock.patch('distro_tracker.core.utils.http.requests')
  457. def test_developer_did_not_exist(self, mock_requests):
  458. """
  459. Tests updating the DM list when a new developer is to be added.
  460. """
  461. set_mock_response(
  462. mock_requests,
  463. "Fingerprint: CFC5B232C0D082CAE6B3A166F04CEFF6016CFFD0\n"
  464. "Uid: Dummy Developer <dummy@debian.org>\n"
  465. "Allow: dummy-package (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),\n"
  466. " second-package (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E)\n")
  467. run_task(RetrieveDebianMaintainersTask)
  468. # A Debian developer created
  469. self.assertEqual(DebianContributor.objects.count(), 1)
  470. d = DebianContributor.objects.all()[0]
  471. self.assertTrue(d.is_debian_maintainer)
  472. self.assertSequenceEqual(
  473. ['dummy-package', 'second-package'],
  474. d.allowed_packages
  475. )
  476. @mock.patch('distro_tracker.core.utils.http.requests')
  477. def test_developer_existed(self, mock_requests):
  478. """
  479. Tests updating the DM list when the developer was previously registered
  480. in the database.
  481. """
  482. UserEmail.objects.create(email='dummy@debian.org')
  483. set_mock_response(
  484. mock_requests,
  485. "Fingerprint: CFC5B232C0D082CAE6B3A166F04CEFF6016CFFD0\n"
  486. "Uid: Dummy Developer <dummy@debian.org>\n"
  487. "Allow: dummy-package (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),\n"
  488. " second-package (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E)\n")
  489. run_task(RetrieveDebianMaintainersTask)
  490. # A Debian developer created
  491. self.assertEqual(DebianContributor.objects.count(), 1)
  492. d = DebianContributor.objects.all()[0]
  493. self.assertTrue(d.is_debian_maintainer)
  494. self.assertSequenceEqual(
  495. ['dummy-package', 'second-package'],
  496. d.allowed_packages
  497. )
  498. @mock.patch('distro_tracker.core.utils.http.requests')
  499. def test_developer_update_dm_list(self, mock_requests):
  500. """
  501. Tests updating the DM list when one of the developers has changes in
  502. the allowed packages list.
  503. """
  504. # Set up a Debian developer that is already in the NMU list.
  505. email = UserEmail.objects.create(email='dummy@debian.org')
  506. DebianContributor.objects.create(email=email,
  507. is_debian_maintainer=True,
  508. allowed_packages=['one'])
  509. set_mock_response(
  510. mock_requests,
  511. "Fingerprint: CFC5B232C0D082CAE6B3A166F04CEFF6016CFFD0\n"
  512. "Uid: Dummy Developer <dummy@debian.org>\n"
  513. "Allow: dummy-package (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),\n"
  514. " second-package (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E)\n")
  515. run_task(RetrieveDebianMaintainersTask)
  516. d = DebianContributor.objects.get(email__email='dummy@debian.org')
  517. # The old package is no longer in its list of allowed packages.
  518. self.assertSequenceEqual(
  519. ['dummy-package', 'second-package'],
  520. d.allowed_packages
  521. )
  522. @mock.patch('distro_tracker.core.utils.http.requests')
  523. def test_developer_delete_from_dm_list(self, mock_requests):
  524. """
  525. Tests updating the DM list when one of the developers has changes in
  526. the allowed packages list.
  527. """
  528. # Set up a Debian developer that is already in the DM list.
  529. email = UserEmail.objects.create(email='dummy@debian.org')
  530. DebianContributor.objects.create(email=email,
  531. is_debian_maintainer=True,
  532. allowed_packages=['one'])
  533. set_mock_response(
  534. mock_requests,
  535. "Fingerprint: CFC5B232C0D082CAE6B3A166F04CEFF6016CFFD0\n"
  536. "Uid: Dummy Developer <different-developer@debian.org>\n"
  537. "Allow: dummy-package (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),\n"
  538. " second-package (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E)\n")
  539. run_task(RetrieveDebianMaintainersTask)
  540. d = DebianContributor.objects.get(email__email='dummy@debian.org')
  541. # The developer is no longer a debian maintainer
  542. self.assertFalse(d.is_debian_maintainer)
  543. class DebianContributorExtraTest(TestCase):
  544. def test_maintainer_extra(self):
  545. email = UserEmail.objects.create(email='dummy@debian.org')
  546. d = DebianContributor.objects.create(email=email,
  547. agree_with_low_threshold_nmu=True)
  548. expected = [
  549. {
  550. 'display': 'DMD',
  551. 'description': 'UDD\'s Debian Maintainer Dashboard',
  552. 'link': 'https://udd.debian.org/dmd/?dummy%40debian.org#todo',
  553. },
  554. {
  555. 'display': 'LowNMU',
  556. 'description': 'maintainer agrees with Low Threshold NMU',
  557. 'link': 'https://wiki.debian.org/LowThresholdNmu',
  558. }
  559. ]
  560. # Only in NMU list
  561. self.assertSequenceEqual(expected,
  562. get_maintainer_extra('dummy@debian.org'))
  563. # The developer is now in the DM list
  564. d.is_debian_maintainer = True
  565. d.allowed_packages = ['package-name']
  566. d.save()
  567. # When not providing a package name, the response is the same
  568. self.assertSequenceEqual(expected,
  569. get_maintainer_extra('dummy@debian.org'))
  570. # With a package name an extra item is in the response.
  571. expected.append({'display': 'dm'})
  572. self.assertSequenceEqual(
  573. expected,
  574. get_maintainer_extra('dummy@debian.org', 'package-name')
  575. )
  576. def test_uploader_extra(self):
  577. email = UserEmail.objects.create(email='dummy@debian.org')
  578. d = DebianContributor.objects.create(email=email,
  579. agree_with_low_threshold_nmu=True)
  580. expected = [
  581. {
  582. 'display': 'DMD',
  583. 'description': 'UDD\'s Debian Maintainer Dashboard',
  584. 'link': 'https://udd.debian.org/dmd/?dummy%40debian.org#todo',
  585. },
  586. ]
  587. # Only in NMU list - no extra data when the developer in displayed as
  588. # an uploader.
  589. self.assertSequenceEqual(expected,
  590. get_uploader_extra('dummy@debian.org'))
  591. # The developer is now in the DM list
  592. d.is_debian_maintainer = True
  593. d.allowed_packages = ['package-name']
  594. d.save()
  595. # When not providing a package name, the response is the same
  596. self.assertSequenceEqual(expected,
  597. get_uploader_extra('dummy@debian.org'))
  598. # With a package name an extra item is in the response.
  599. expected.append({'display': 'dm'})
  600. self.assertSequenceEqual(
  601. expected,
  602. get_uploader_extra('dummy@debian.org', 'package-name')
  603. )
  604. @override_settings(
  605. DISTRO_TRACKER_VENDOR_RULES='distro_tracker.vendor.debian.rules')
  606. class RetrieveSourcesInformationDebian(TestCase):
  607. """
  608. Tests the Debian-specific aspects of retrieving package information from a
  609. repository.
  610. """
  611. fixtures = ['repository-test-fixture.json']
  612. def setUp(self):
  613. self.repository = Repository.objects.all()[0]
  614. @mock.patch(
  615. 'distro_tracker.core.retrieve_data.AptCache.update_repositories')
  616. def test_extra_source_only_ignored(self, mock_update_repositories):
  617. """
  618. Tests that the packages with the 'Extra-Source-Only' key are ignored.
  619. """
  620. sources_contents = (
  621. """Package: dummy-package
  622. Binary: dummy-package-binary
  623. Version: 1.0.0
  624. Maintainer: Maintainer <maintainer@domain.com>
  625. Architecture: all amd64
  626. Files:
  627. 22700cab41effa76f45968aeee39cdb1 3041 file.dsc
  628. Package: src-pkg
  629. Binary: other-package
  630. Version: 2.2
  631. Maintainer: Maintainer <maintainer@domain.com>
  632. Architecture: all amd64
  633. Extra-Source-Only: yes
  634. Files:
  635. 227ffeabc4357876f45968aeee39cdb1 3041 file.dsc
  636. """)
  637. with make_temp_directory('-mock-repo-cache') as temp_dir_name:
  638. sources_file_path = os.path.join(temp_dir_name, 'Sources')
  639. with open(sources_file_path, 'w') as f:
  640. f.write(sources_contents)
  641. mock_update_repositories.return_value = (
  642. [(self.repository, sources_file_path)],
  643. []
  644. )
  645. # Sanity check - no source packages before running the task
  646. self.assertEqual(0, SourcePackageName.objects.count())
  647. run_task(UpdateRepositoriesTask)
  648. # Only one package exists
  649. self.assertEqual(1, SourcePackageName.objects.count())
  650. # It is the one without the Extra-Source-Only: yes
  651. self.assertEqual(
  652. 'dummy-package',
  653. SourcePackageName.objects.all()[0].name)
  654. @override_settings(
  655. DISTRO_TRACKER_VENDOR_RULES='distro_tracker.vendor.debian.rules')
  656. class DebianNewsFromEmailTest(TestCase):
  657. """
  658. Tests creating Debian-specific news from received emails.
  659. """
  660. def setUp(self):
  661. self.package_name = SourcePackageName.objects.create(
  662. name='dummy-package')
  663. self.package = SourcePackage.objects.create(
  664. source_package_name=self.package_name, version='1.0.0')
  665. self.message = Message()
  666. def set_subject(self, subject):
  667. if 'Subject' in self.message:
  668. del self.message['Subject']
  669. self.message['Subject'] = subject
  670. def add_header(self, header_name, header_value):
  671. self.message[header_name] = header_value
  672. def set_message_content(self, content):
  673. self.message.set_payload(content)
  674. def process_mail(self):
  675. process(force_bytes(self.message.as_string(), 'utf-8'))
  676. def get_accepted_subject(self, pkg, version):
  677. """
  678. Helper method returning the subject of an email notifying of a new
  679. source upload.
  680. """
  681. return 'Accepted {pkg} {ver} (source all)'.format(pkg=pkg, ver=version)
  682. def get_removed_from_testing_subject(self, pkg):
  683. """
  684. Helper method providing the subject of an email from testing watch.
  685. """
  686. return '{pkg} REMOVED from testing'.format(pkg=pkg)
  687. def test_source_upload_news(self):
  688. """
  689. Tests the news created when a notification of a new source upload is
  690. received.
  691. """
  692. subject = self.get_accepted_subject(
  693. self.package_name, self.package.version)
  694. self.set_subject(subject)
  695. content = b'Content'
  696. self.set_message_content(content)
  697. self.process_mail()
  698. self.assertEqual(1, News.objects.count())
  699. news = News.objects.all()[0]
  700. self.assertEqual(news.package.name, self.package.name)
  701. self.assertEqual(subject, news.title)
  702. self.assertIn(content, news.content)
  703. def test_source_upload_package_does_not_exist(self):
  704. """
  705. Tests that a news and the associated source package are created when
  706. the notification of a new source upload for a package not yet tracked by
  707. Distro Tracker is received.
  708. """
  709. subject = self.get_accepted_subject('no-exist', '1.0.0')
  710. self.set_subject(subject)
  711. content = 'Content'
  712. self.set_message_content(content)
  713. self.process_mail()
  714. self.assertTrue(PackageName.objects.filter(name='no-exist').exists())
  715. self.assertEqual(1, News.objects.count())
  716. def test_dak_rm_news(self):
  717. """
  718. Tests that a dak rm message creates a news.
  719. """
  720. subject = 'Removed package(s) from unstable'
  721. self.set_subject(subject)
  722. content = (
  723. 'We believe that the bug you reported is now fixed; the following\n'
  724. 'package(s) have been removed from unstable:\n\n'
  725. '{pkg} | {ver} | source, all').format(
  726. pkg=self.package_name,
  727. ver=self.package.version)
  728. self.set_message_content(content)
  729. self.add_header('X-DAK', 'dak rm')
  730. self.add_header('X-Debian', 'DAK')
  731. sender = 'Some Sender <email@domain.com>'
  732. self.add_header('Sender', sender)
  733. self.process_mail()
  734. self.assertEqual(1, News.objects.count())
  735. news = News.objects.all()[0]
  736. self.assertEqual(news.package.name, self.package.name)
  737. self.assertEqual(news.title, 'Removed {ver} from unstable'.format(
  738. ver=self.package.version))
  739. def test_dak_rm_no_package(self):
  740. """
  741. Tests that a dak rm message referencing a package which Distro
  742. Tracker does not track, does not create any news.
  743. """
  744. subject = 'Removed package(s) from unstable'
  745. self.set_subject(subject)
  746. content = (
  747. 'We believe that the bug you reported is now fixed; the following\n'
  748. 'package(s) have been removed from unstable:\n\n'
  749. '{pkg} | {ver} | source, all').format(
  750. pkg='does-not-exist',
  751. ver='1.0.0')
  752. self.set_message_content(content)
  753. self.add_header('X-DAK', 'dak rm')
  754. self.add_header('X-Debian', 'DAK')
  755. sender = 'Some Sender <email@domain.com>'
  756. self.add_header('Sender', sender)
  757. self.process_mail()
  758. self.assertEqual(0, News.objects.count())
  759. def test_dak_not_rm(self):
  760. """
  761. Tests that a message with an X-DAK header different from ``dak rm``
  762. does not create any news item.
  763. """
  764. subject = 'Removed package(s) from unstable'
  765. self.set_subject(subject)
  766. content = (
  767. 'We believe that the bug you reported is now fixed; the following\n'
  768. 'package(s) have been removed from unstable:\n\n'
  769. '{pkg} | {ver} | source, all').format(
  770. pkg=self.package_name,
  771. ver=self.package.version)
  772. self.set_message_content(content)
  773. self.add_header('X-DAK', 'dak somethingelse')
  774. self.add_header('X-Debian', 'DAK')
  775. sender = 'Some Sender <email@domain.com>'
  776. self.add_header('Sender', sender)
  777. self.process_mail()
  778. self.assertEqual(0, News.objects.count())
  779. def test_multiple_removes(self):
  780. """
  781. Tests that multiple news items are created when the dak rm message
  782. contains multiple remove notifications.
  783. """
  784. subject = 'Removed package(s) from unstable'
  785. self.set_subject(subject)
  786. content = (
  787. 'We believe that the bug you reported is now fixed; the following\n'
  788. 'package(s) have been removed from unstable:\n\n')
  789. content += (
  790. '{pkg} | {ver} | source, all\n'
  791. ).format(pkg=self.package_name, ver=self.package.version)
  792. content += (
  793. '{pkg} | {ver} | source, all\n'
  794. ).format(pkg=self.package_name, ver='2.0.0')
  795. self.set_message_content(content)
  796. self.add_header('X-DAK', 'dak rm')
  797. self.add_header('X-Debian', 'DAK')
  798. sender = 'Some Sender <email@domain.com>'
  799. self.add_header('Sender', sender)
  800. self.process_mail()
  801. self.assertEqual(2, News.objects.count())
  802. def test_testing_watch_news(self):
  803. """
  804. Tests that an email received from the Testing Watch is turned into a
  805. news item.
  806. """
  807. subject = self.get_removed_from_testing_subject(self.package_name)
  808. self.set_subject(subject)
  809. content = (
  810. "FYI: The status of the {pkg} source package\n"
  811. "in Debian's testing distribution has changed.\n\n"
  812. " Previous version: 1.0.0\n"
  813. " Current version: (not in testing)\n"
  814. " Hint: some hint..."
  815. ).format(pkg=self.package_name).encode('utf-8')
  816. self.set_message_content(content)
  817. self.add_header('X-Testing-Watch-Package', self.package.name)
  818. self.process_mail()
  819. self.assertEqual(1, News.objects.count())
  820. news = News.objects.all()[0]
  821. self.assertEqual(subject, news.title)
  822. self.assertIn(content, news.content)
  823. def test_testing_watch_package_no_exist(self):
  824. """
  825. Tests that an email received from the Testing Watch which references
  826. a package not tracked by Distro Tracker does not create any news items.
  827. """
  828. subject = self.get_removed_from_testing_subject('no-exist')
  829. self.set_subject(subject)
  830. content = (
  831. "FYI: The status of the {pkg} source package\n"
  832. "in Debian's testing distribution has changed.\n\n"
  833. " Previous version: 1.0.0\n"
  834. " Current version: (not in testing)\n"
  835. " Hint: some hint..."
  836. ).format(pkg='no-exist')
  837. self.set_message_content(content)
  838. self.add_header('X-Testing-Watch-Package', 'no-exist')
  839. self.process_mail()
  840. self.assertEqual(0, News.objects.count())
  841. class UpdateLintianStatsTaskTest(TestCase):
  842. """
  843. Tests for the
  844. :class:`distro_tracker.vendor.debian.tracker_tasks.UpdateLintianStatsTask`
  845. task.
  846. """
  847. def setUp(self):
  848. self.package_name = SourcePackageName.objects.create(
  849. name='dummy-package')
  850. self.package = SourcePackage(
  851. source_package_name=self.package_name, version='1.0.0')
  852. def run_task(self):
  853. """
  854. Runs the lintian stats update task.
  855. """
  856. task = UpdateLintianStatsTask()
  857. task.execute()
  858. def assert_correct_category_stats(self, stats, expected_stats):
  859. """
  860. Helper method which asserts that the given stats match the expected
  861. stats.
  862. :param stats: Mapping category names to count
  863. :type stats: dict
  864. :param expected_stats: A list of counts as given by the Web lintian
  865. resource
  866. :type expected_stats: list
  867. """
  868. categories = (
  869. 'errors',
  870. 'warnings',
  871. 'pedantics',
  872. 'experimentals',
  873. 'overriddens',
  874. )
  875. for category, count in zip(categories, expected_stats):
  876. self.assertEqual(stats[category], count)
  877. def assert_action_item_warnings_and_errors_count(
  878. self,
  879. item,
  880. errors=0,
  881. warnings=0):
  882. """
  883. Helper method which checks if an instance of
  884. :class:`distro_tracker.core.ActionItem` contains the given error and
  885. warning count in its extra_data.
  886. """
  887. self.assertEqual(item.extra_data['errors'], errors)
  888. self.assertEqual(item.extra_data['warnings'], warnings)
  889. def get_action_item_type(self):
  890. return ActionItemType.objects.get_or_create(
  891. type_name=UpdateLintianStatsTask.ACTION_ITEM_TYPE_NAME)[0]
  892. @mock.patch('distro_tracker.core.utils.http.requests')
  893. def test_stats_created(self, mock_requests):
  894. """
  895. Tests that stats are created for a package that previously did not have
  896. any lintian stats.
  897. """
  898. set_mock_response(mock_requests, text="dummy-package 1 2 3 4 5 6")
  899. self.run_task()
  900. # The stats have been created
  901. self.assertEqual(1, LintianStats.objects.count())
  902. # They are associated with the correct package.
  903. stats = LintianStats.objects.all()[0]
  904. self.assertEqual(stats.package.name, 'dummy-package')
  905. # The category counts themselves are correct
  906. self.assert_correct_category_stats(stats.stats, [1, 2, 3, 4, 5, 6])
  907. @mock.patch('distro_tracker.core.utils.http.requests')
  908. def test_stats_updated(self, mock_requests):
  909. """
  910. Tests that when a package already had associated linian stats, they are
  911. correctly updated after running the task.
  912. """
  913. set_mock_response(mock_requests, text="dummy-package 6 5 4 3 2 1")
  914. # Create the pre-existing stats for the package
  915. LintianStats.objects.create(
  916. package=self.package_name, stats=[1, 2, 3, 4, 5, 6])
  917. self.run_task()
  918. # Still only one lintian stats object
  919. self.assertEqual(1, LintianStats.objects.count())
  920. # The package is still correct
  921. stats = LintianStats.objects.all()[0]
  922. self.assertEqual(stats.package.name, 'dummy-package')
  923. # The stats have been updated
  924. self.assert_correct_category_stats(stats.stats, [6, 5, 4, 3, 2, 1])
  925. @mock.patch('distro_tracker.core.utils.http.requests')
  926. def test_stats_created_multiple_packages(self, mock_requests):
  927. """
  928. Tests that stats are correctly creatd when there are stats for
  929. multiple packages in the response.
  930. """
  931. # Create a second package.
  932. SourcePackageName.objects.create(name='other-package')
  933. response = (
  934. "dummy-package 6 5 4 3 2 1\n"
  935. "other-package 1 2 3 4 5 6"
  936. )
  937. set_mock_response(mock_requests, text=response)
  938. self.run_task()
  939. # Stats created for both packages
  940. self.assertEqual(2, LintianStats.objects.count())
  941. all_names = [stats.package.name
  942. for stats in LintianStats.objects.all()]
  943. self.assertIn('dummy-package', all_names)
  944. self.assertIn('other-package', all_names)
  945. @mock.patch('distro_tracker.core.utils.http.requests')
  946. def test_unknown_package(self, mock_requests):
  947. """
  948. Tests that when an unknown package is encountered, no stats are created.
  949. """
  950. set_mock_response(mock_requests, text="no-exist 1 2 3 4 5 6")
  951. self.run_task()
  952. # There are no stats
  953. self.assertEqual(0, LintianStats.objects.count())
  954. @mock.patch('distro_tracker.core.utils.http.requests')
  955. def test_parse_error(self, mock_requests):
  956. """
  957. Tests that when a parse error is encountered for a single package, it
  958. is skipped without affected the rest of the packages in the response.
  959. """
  960. # Create a second package.
  961. SourcePackageName.objects.create(name='other-package')
  962. response = (
  963. "dummy-package 6 5 4 3 2 1\n"
  964. "other-package 1 2 a 4 5 6"
  965. )
  966. set_mock_response(mock_requests, text=response)
  967. self.run_task()
  968. # Only one package has stats
  969. self.assertEqual(1, LintianStats.objects.count())
  970. stats = LintianStats.objects.all()[0]
  971. self.assertEqual(stats.package.name, 'dummy-package')
  972. @mock.patch('distro_tracker.core.utils.http.requests')
  973. def test_correct_url_used(self, mock_requests):
  974. """
  975. Tests that lintian stats are retrieved from the correct URL.
  976. """
  977. self.run_task()
  978. # We only care about the URL used, not the headers or other arguments
  979. self.assertEqual(
  980. mock_requests.get.call_args[0][0],
  981. 'https://lintian.debian.org/qa-list.txt')
  982. @mock.patch('distro_tracker.core.utils.http.requests')
  983. def test_action_item_created_errors(self, mock_requests):
  984. """
  985. Tests that an action item is created when the package has errors.
  986. """
  987. errors, warnings = 2, 0
  988. response = "dummy-package {err} {warn} 0 0 0 0".format(
  989. err=errors, warn=warnings)
  990. set_mock_response(mock_requests, text=response)
  991. # Sanity check: there were no action items in the beginning
  992. self.assertEqual(0, ActionItem.objects.count())
  993. self.run_task()
  994. # An action item is created.
  995. self.assertEqual(1, ActionItem.objects.count())
  996. # The correct number of errors and warnings is stored in the item
  997. item = ActionItem.objects.all()[0]
  998. self.assert_action_item_warnings_and_errors_count(
  999. item,
  1000. errors,
  1001. warnings)
  1002. # It has the correct type
  1003. self.assertEqual(
  1004. item.item_type.type_name,
  1005. UpdateLintianStatsTask.ACTION_ITEM_TYPE_NAME)
  1006. # It is a high severity issue
  1007. self.assertEqual('high', item.get_severity_display())
  1008. # Correct full description template
  1009. self.assertEqual(
  1010. item.full_description_template,
  1011. UpdateLintianStatsTask.ITEM_FULL_DESCRIPTION_TEMPLATE)
  1012. @mock.patch('distro_tracker.core.utils.http.requests')
  1013. def test_action_item_updated(self, mock_requests):
  1014. """
  1015. Tests that an existing action item is updated with new data.
  1016. """
  1017. # Create an existing action item
  1018. old_item = ActionItem.objects.create(
  1019. package=self.package_name,
  1020. item_type=self.get_action_item_type(),
  1021. short_description="Short description...",
  1022. extra_data={'errors': 1, 'warnings': 2})
  1023. old_timestamp = old_item.last_updated_timestamp
  1024. errors, warnings = 2, 0
  1025. response = "dummy-package {err} {warn} 0 0 0 0".format(
  1026. err=errors, warn=warnings)
  1027. set_mock_response(mock_requests, text=response)
  1028. self.run_task()
  1029. # An action item is created.
  1030. self.assertEqual(1, ActionItem.objects.count())
  1031. # Extra data updated?
  1032. item = ActionItem.objects.all()[0]
  1033. self.assert_action_item_warnings_and_errors_count(
  1034. item,
  1035. errors,
  1036. warnings)
  1037. # The timestamp is updated
  1038. self.assertNotEqual(old_timestamp, item.last_updated_timestamp)
  1039. @mock.patch('distro_tracker.core.utils.http.requests')
  1040. def test_action_item_not_updated(self, mock_requests):
  1041. """
  1042. Tests that an existing action item is left unchanged when the update
  1043. shows unchanged lintian stats.
  1044. """
  1045. errors, warnings = 2, 0
  1046. # Create an existing action item
  1047. old_item = ActionItem.objects.create(
  1048. package=self.package_name,
  1049. item_type=self.get_action_item_type(),
  1050. short_description="Short description...",
  1051. extra_data={'errors': errors, 'warnings': warnings})
  1052. old_timestamp = old_item.last_updated_timestamp
  1053. response = "dummy-package {err} {warn} 0 0 0 0".format(
  1054. err=errors, warn=warnings)
  1055. set_mock_response(mock_requests, text=response)
  1056. self.run_task()
  1057. # An action item is created.
  1058. self.assertEqual(1, ActionItem.objects.count())
  1059. # Item unchanged?
  1060. item = ActionItem.objects.all()[0]
  1061. self.assertEqual(old_timestamp, item.last_updated_timestamp)
  1062. @mock.patch('distro_tracker.core.utils.http.requests')
  1063. def test_action_item_created_warnings(self, mock_requests):
  1064. """
  1065. Tests that an action item is created when the package has warnings.
  1066. """
  1067. errors, warnings = 0, 2
  1068. response = "dummy-package {err} {warn} 0 0 0 0".format(
  1069. err=errors, warn=warnings)
  1070. set_mock_response(mock_requests, text=response)
  1071. # Sanity check: there were no action items in the beginning
  1072. self.assertEqual(0, ActionItem.objects.count())
  1073. self.run_task()
  1074. # An action item is created.
  1075. self.assertEqual(1, ActionItem.objects.count())
  1076. # The correct number of errors and warnings is stored in the item
  1077. item = ActionItem.objects.all()[0]
  1078. self.assert_action_item_warnings_and_errors_count(
  1079. item,
  1080. errors,
  1081. warnings)
  1082. # It is a normal severity issue
  1083. self.assertEqual('normal', item.get_severity_display())
  1084. @mock.patch('distro_tracker.core.utils.http.requests')
  1085. def test_action_item_created_errors_and_warnings(self, mock_requests):
  1086. """
  1087. Tests that an action item is created when the package has errors and
  1088. warnings.
  1089. """
  1090. errors, warnings = 2, 2
  1091. response = "dummy-package {err} {warn} 0 0 0 0".format(
  1092. err=errors, warn=warnings)
  1093. set_mock_response(mock_requests, text=response)
  1094. # Sanity check: there were no action items in the beginning
  1095. self.assertEqual(0, ActionItem.objects.count())
  1096. self.run_task()
  1097. # An action item is created.
  1098. self.assertEqual(1, ActionItem.objects.count())
  1099. # The item is linked to the correct package
  1100. item = ActionItem.objects.all()[0]
  1101. self.assertEqual(item.package.name, self.package_name.name)
  1102. # The correct number of errors and warnings is stored in the item
  1103. self.assert_action_item_warnings_and_errors_count(
  1104. item,
  1105. errors,
  1106. warnings)
  1107. # It is a high severity issue since it contains both errors and
  1108. # warnings
  1109. self.assertEqual('high', item.get_severity_display())
  1110. @mock.patch('distro_tracker.core.utils.http.requests')
  1111. def test_action_item_not_created(self, mock_requests):
  1112. """
  1113. Tests that no action item is created when the package has no errors or
  1114. warnings.
  1115. """
  1116. response = "dummy-package 0 0 5 4 3 2"
  1117. set_mock_response(mock_requests, text=response)
  1118. # Sanity check: there were no action items in the beginning
  1119. self.assertEqual(0, ActionItem.objects.count())
  1120. self.run_task()
  1121. # Still no action items.
  1122. self.assertEqual(0, ActionItem.objects.count())
  1123. @mock.patch('distro_tracker.core.utils.http.requests')
  1124. def test_action_item_removed(self, mock_requests):
  1125. """
  1126. Tests that a previously existing action item is removed if the updated
  1127. stats no longer contain errors or warnings.
  1128. """
  1129. # Make sure an item exists for the package
  1130. ActionItem.objects.create(
  1131. package=self.package_name,
  1132. item_type=self.get_action_item_type(),
  1133. short_description="Short description...",
  1134. extra_data={'errors': 1, 'warnings': 2})
  1135. response = "dummy-package 0 0 5 4 3 2"
  1136. set_mock_response(mock_requests, text=response)
  1137. self.run_task()
  1138. # There are no action items any longer.
  1139. self.assertEqual(0, self.package_name.action_items.count())
  1140. @mock.patch('distro_tracker.core.utils.http.requests')
  1141. def test_action_item_removed_no_data(self, mock_requests):
  1142. """
  1143. Tests that a previously existing action item is removed when the
  1144. updated stats no longer contain any information for the package.
  1145. """
  1146. item_type, _ = ActionItemType.objects.get_or_create(
  1147. type_name=UpdateLintianStatsTask.ACTION_ITEM_TYPE_NAME)
  1148. ActionItem.objects.create(
  1149. package=self.package_name,
  1150. item_type=item_type,
  1151. short_description="Short description...",
  1152. extra_data={'errors': 1, 'warnings': 2})
  1153. response = "some-package 0 0 5 4 3 2"
  1154. set_mock_response(mock_requests, text=response)
  1155. self.run_task()
  1156. # There are no action items any longer.
  1157. self.assertEqual(0, self.package_name.action_items.count())
  1158. @mock.patch('distro_tracker.core.utils.http.requests')
  1159. def test_action_item_created_multiple_packages(self, mock_requests):
  1160. """
  1161. Tests that action items are created correctly when there are stats
  1162. for multiple different packages in the response.
  1163. """
  1164. other_package = PackageName.objects.create(
  1165. name='other-package',
  1166. source=True)
  1167. errors, warnings = (2, 0), (0, 2)
  1168. response = (
  1169. "dummy-package {err1} {warn1} 0 0 0 0\n"
  1170. "other-package {err2} {warn2} 0 0 0 0"
  1171. "some-package 0 0 0 0 0 0".format(
  1172. err1=errors[0], warn1=warnings[0],
  1173. err2=errors[1], warn2=warnings[1])
  1174. )
  1175. set_mock_response(mock_requests, text=response)
  1176. # Sanity check: there were no action items in the beginning
  1177. self.assertEqual(0, ActionItem.objects.count())
  1178. self.run_task()
  1179. # Action items are created for two packages.
  1180. self.assertEqual(1, self.package_name.action_items.count())
  1181. self.assertEqual(1, other_package.action_items.count())
  1182. # The items contain correct data.
  1183. item = self.package_name.action_items.all()[0]
  1184. self.assert_action_item_warnings_and_errors_count(
  1185. item,
  1186. errors[0],
  1187. warnings[0])
  1188. item = other_package.action_items.all()[0]
  1189. self.assert_action_item_warnings_and_errors_count(
  1190. item,
  1191. errors[1],
  1192. warnings[1])
  1193. @mock.patch('distro_tracker.core.utils.http.requests')
  1194. def test_update_does_not_affect_other_item_types(self, mock_requests):
  1195. """
  1196. Tests that running an update of lintian stats does not cause other
  1197. package categories to be removed.
  1198. """
  1199. # Create an item for the package with a different type.
  1200. other_type = ActionItemType.objects.create(type_name='other-type')
  1201. ActionItem.objects.create(
  1202. item_type=other_type,
  1203. package=self.package_name,
  1204. short_description='Desc.')
  1205. errors, warnings = 2, 0
  1206. response = "dummy-package {err} {warn} 0 0 0 0".format(
  1207. err=errors, warn=warnings)
  1208. set_mock_response(mock_requests, text=response)
  1209. # Sanity check: exactly one action item in the beginning
  1210. self.assertEqual(1, ActionItem.objects.count())
  1211. self.run_task()
  1212. # An action item is created.
  1213. self.assertEqual(2, self.package_name.action_items.count())
  1214. class DebianBugActionItemsTests(TestCase):
  1215. """
  1216. Tests the creation of :class:`distro_tracker.core.ActionItem` instances
  1217. based on Debian bug stats.
  1218. """
  1219. @staticmethod
  1220. def stub_tagged_bugs(tag, user=None, help_bugs=None, newcomer_bugs=None):
  1221. if tag == 'help':
  1222. return help_bugs
  1223. elif tag == 'newcomer':
  1224. return newcomer_bugs
  1225. def setUp(self):
  1226. self.package_name = SourcePackageName.objects.create(
  1227. name='dummy-package')
  1228. self.package = SourcePackage(
  1229. source_package_name=self.package_name, version='1.0.0')
  1230. self.task = UpdatePackageBugStats()
  1231. self.udd_bugs = {}
  1232. self.help_bugs = {}
  1233. self.newcomer_bugs = {}
  1234. # Stub the data providing methods
  1235. self.task._get_udd_bug_stats = mock.MagicMock(
  1236. return_value=self.udd_bugs)
  1237. self.task._get_tagged_bug_stats = mock.MagicMock(
  1238. side_effect=curry(
  1239. DebianBugActionItemsTests.stub_tagged_bugs,
  1240. help_bugs=self.help_bugs,
  1241. newcomer_bugs=self.newcomer_bugs))
  1242. # Ignore binary package bugs for action item tests.
  1243. self.task.update_binary_bugs = mock.MagicMock()
  1244. def run_task(self):
  1245. self.task.execute()
  1246. def add_patch_bug(self, package, bug_count):
  1247. """
  1248. Helper method adding patch bugs to the stub return value.
  1249. """
  1250. self.add_udd_bug_category(package, 'patch', bug_count)
  1251. def add_udd_bug_category(self, package, category, bug_count):
  1252. """
  1253. Adds stats for a bug category to the stub response, as if the category
  1254. was found in the UDD bug stats.
  1255. """
  1256. self.udd_bugs.setdefault(package, [])
  1257. self.udd_bugs[package].append({
  1258. 'category_name': category,
  1259. 'bug_count': bug_count,
  1260. })
  1261. def add_help_bug(self, package, bug_count):
  1262. """
  1263. Helper method adding help bugs to the stub return value.
  1264. """
  1265. self.help_bugs[package] = bug_count
  1266. def get_patch_action_type(self):
  1267. """
  1268. Helper method returning a
  1269. :class:`distro_tracker.core.models.ActionItemType` for the debian patch
  1270. bug warning action item type.
  1271. """
  1272. return ActionItemType.objects.get_or_create(
  1273. type_name=UpdatePackageBugStats.PATCH_BUG_ACTION_ITEM_TYPE_NAME)[0]
  1274. def get_help_action_type(self):
  1275. """
  1276. Helper method returning a
  1277. :class:`distro_tracker.core.models.ActionItemType` for the debian help
  1278. bug warning action item type.
  1279. """
  1280. return ActionItemType.objects.get_or_create(
  1281. type_name=UpdatePackageBugStats.HELP_BUG_ACTION_ITEM_TYPE_NAME)[0]
  1282. def test_patch_bug_action_item(self):
  1283. """
  1284. Tests that an action item is created when there are bugs tagged patch.
  1285. """
  1286. bug_count = 2
  1287. self.add_patch_bug(self.package_name.name, bug_count)
  1288. # Sanity check: no items
  1289. self.assertEqual(0, ActionItem.objects.count())
  1290. self.run_task()
  1291. # Action item created.
  1292. self.assertEqual(1, ActionItem.objects.count())
  1293. # The item is of the correct type
  1294. item = ActionItem.objects.all()[0]
  1295. self.assertEqual(
  1296. item.item_type.type_name,
  1297. UpdatePackageBugStats.PATCH_BUG_ACTION_ITEM_TYPE_NAME)
  1298. # The item references the correct package.
  1299. self.assertEqual(item.package.name, self.package_name.name)
  1300. # It contains the extra data
  1301. self.assertEqual(item.extra_data['bug_count'], bug_count)
  1302. # Correct full description template
  1303. self.assertEqual(
  1304. item.full_description_template,
  1305. UpdatePackageBugStats.PATCH_ITEM_FULL_DESCRIPTION_TEMPLATE)
  1306. def test_patch_bug_action_item_updated(self):
  1307. """
  1308. Tests that an already existing action item is updated after running the
  1309. task.
  1310. """
  1311. # Create a previously existing action item for the patch type.
  1312. ActionItem.objects.create(
  1313. package=self.package_name,
  1314. item_type=self.get_patch_action_type(),
  1315. short_description="Desc")
  1316. bug_count = 2
  1317. self.add_patch_bug(self.package_name.name, bug_count)
  1318. self.run_task()
  1319. # Still only one item...
  1320. self.assertEqual(1, self.package_name.action_items.count())
  1321. # It contains updated data
  1322. item = self.package_name.action_items.all()[0]
  1323. self.assertEqual(item.extra_data['bug_count'], bug_count)
  1324. def test_patch_bug_action_item_removed(self):
  1325. """
  1326. Tests that an already existing action item is removed after the update
  1327. does not contain any more bugs in the patch category.
  1328. """
  1329. # Create a previously existing action item for the patch type.
  1330. ActionItem.objects.create(
  1331. package=self.package_name,
  1332. item_type=self.get_patch_action_type(),
  1333. short_description="Desc")
  1334. bug_count = 0
  1335. self.add_patch_bug(self.package_name.name, bug_count)
  1336. self.run_task()
  1337. # No more action items.
  1338. self.assertEqual(0, self.package_name.action_items.count())
  1339. def test_patch_bug_action_item_removed_no_data(self):
  1340. """
  1341. Tests that an already existing action item is removed if the update
  1342. does not give any stats at all.
  1343. """
  1344. # Create a previously existing action item for the patch type.
  1345. ActionItem.objects.create(
  1346. package=self.package_name,
  1347. item_type=self.get_patch_action_type(),
  1348. short_description="Desc")
  1349. self.run_task()
  1350. # No more action items.
  1351. self.assertEqual(0, self.package_name.action_items.count())
  1352. def test_patch_bug_action_item_removed_no_data_for_category(self):
  1353. """
  1354. Tests that an already existing action item is removed if the update
  1355. does not contain stats for the patch category, but does contain
  1356. stats for different categories.
  1357. """
  1358. # Create a previously existing action item for the patch type.
  1359. ActionItem.objects.create(
  1360. package=self.package_name,
  1361. item_type=self.get_patch_action_type(),
  1362. short_description="Desc")
  1363. self.add_udd_bug_category(self.package_name.name, 'normal', 1)
  1364. self.run_task()
  1365. # No more action items.
  1366. self.assertEqual(0, self.package_name.action_items.count())
  1367. def test_help_bug_action_item(self):
  1368. """
  1369. Tests that an action item is created when there are bugs tagged help.
  1370. """
  1371. bug_count = 2
  1372. self.add_help_bug(self.package_name.name, bug_count)
  1373. # Sanity check: no items
  1374. self.assertEqual(0, ActionItem.objects.count())
  1375. self.run_task()
  1376. # Action item created.
  1377. self.assertEqual(1, ActionItem.objects.count())
  1378. # The item references the correct package.
  1379. item = ActionItem.objects.all()[0]
  1380. self.assertEqual(item.package.name, self.package_name.name)
  1381. # It contains the extra data
  1382. self.assertEqual(item.extra_data['bug_count'], bug_count)
  1383. # Correct full description template
  1384. self.assertEqual(
  1385. item.full_description_template,
  1386. UpdatePackageBugStats.HELP_ITEM_FULL_DESCRIPTION_TEMPLATE)
  1387. def test_help_action_item_updated(self):
  1388. """
  1389. Tests that an already existing action item is updated after running the
  1390. task.
  1391. """
  1392. # Create a previously existing action item for the help type.
  1393. ActionItem.objects.create(
  1394. package=self.package_name,
  1395. item_type=self.get_help_action_type(),
  1396. short_description="Desc")
  1397. bug_count = 2
  1398. self.add_help_bug(self.package_name.name, bug_count)
  1399. self.run_task()
  1400. # Still only one item...
  1401. self.assertEqual(1, self.package_name.action_items.count())
  1402. # It contains updated data
  1403. item = self.package_name.action_items.all()[0]
  1404. self.assertEqual(item.extra_data['bug_count'], bug_count)
  1405. def test_help_bug_action_item_removed(self):
  1406. """
  1407. Tests that an already existing action item is removed after the update
  1408. does not contain any more bugs in the help category.
  1409. """
  1410. # Create a previously existing action item for the patch type.
  1411. ActionItem.objects.create(
  1412. package=self.package_name,
  1413. item_type=self.get_help_action_type(),
  1414. short_description="Desc")
  1415. bug_count = 0
  1416. self.add_help_bug(self.package_name.name, bug_count)
  1417. self.run_task()
  1418. # No more action items.
  1419. self.assertEqual(0, self.package_name.action_items.count())
  1420. def test_help_bug_action_item_removed_no_data(self):
  1421. """
  1422. Tests that an already existing action item is removed if the update
  1423. does not give any stats at all.
  1424. """
  1425. # Create a previously existing action item for the patch type.
  1426. ActionItem.objects.create(
  1427. package=self.package_name,
  1428. item_type=self.get_help_action_type(),
  1429. short_description="Desc")
  1430. self.run_task()
  1431. # No more action items.
  1432. self.assertEqual(0, self.package_name.action_items.count())
  1433. def test_help_bug_action_item_removed_no_data_for_category(self):
  1434. """
  1435. Tests that an already existing action item is removed if the update
  1436. does not contain stats for the help category, but does contain
  1437. stats for different categories.
  1438. """
  1439. # Create a previously existing action item for the patch type.
  1440. ActionItem.objects.create(
  1441. package=self.package_name,
  1442. item_type=self.get_help_action_type(),
  1443. short_description="Desc")
  1444. self.add_udd_bug_category(self.package_name.name, 'normal', 1)
  1445. self.run_task()
  1446. # No more action items.
  1447. self.assertEqual(0, self.package_name.action_items.count())
  1448. def test_multiple_action_items_for_package(self):
  1449. """
  1450. Tests that multiple :class:`distro_tracker.core.models.ActionItem`
  1451. instances are created for a package if it contains both patch and help
  1452. bugs.
  1453. """
  1454. patch_bug_count = 2
  1455. help_bug_count = 5
  1456. self.add_patch_bug(self.package_name.name, patch_bug_count)
  1457. self.add_help_bug(self.package_name.name, help_bug_count)
  1458. # Sanity check: no action items
  1459. self.assertEqual(0, self.package_name.action_items.count())
  1460. self.run_task()
  1461. # Two action items.
  1462. self.assertEqual(2, self.package_name.action_items.count())
  1463. # Correct respective bug counts
  1464. patch_item = self.package_name.action_items.get(
  1465. item_type=self.get_patch_action_type())
  1466. self.assertEqual(patch_item.extra_data['bug_count'], patch_bug_count)
  1467. help_item = self.package_name.action_items.get(
  1468. item_type=self.get_help_action_type())
  1469. self.assertEqual(help_item.extra_data['bug_count'], help_bug_count)
  1470. def test_action_item_for_multiple_packages(self):
  1471. """
  1472. Tests that action items are correctly created when more than one
  1473. package has bug warnings.
  1474. """
  1475. stats = (
  1476. (2, 5),
  1477. (1, 1),
  1478. )
  1479. packages = (
  1480. self.package_name,
  1481. PackageName.objects.create(name='other-package', source=True),
  1482. )
  1483. # Create the stub response
  1484. for package, bug_stats in zip(packages, stats):
  1485. patch_bug_count, help_bug_count = bug_stats
  1486. self.add_patch_bug(package.name, patch_bug_count)
  1487. self.add_help_bug(package.name, help_bug_count)
  1488. self.run_task()
  1489. # Each package has two action items
  1490. for package, bug_stats in zip(packages, stats):
  1491. patch_bug_count, help_bug_count = bug_stats
  1492. self.assertEqual(2, package.action_items.count())
  1493. patch_item = package.action_items.get(
  1494. item_type=self.get_patch_action_type())
  1495. self.assertEqual(
  1496. patch_item.extra_data['bug_count'],
  1497. patch_bug_count)
  1498. help_item = package.action_items.get(
  1499. item_type=self.get_help_action_type())
  1500. self.assertEqual(help_item.extra_data['bug_count'], help_bug_count)
  1501. class UpdateExcusesTaskActionItemTest(TestCase):
  1502. """
  1503. Tests for the creating of action items by the
  1504. :class:`distro_tracker.vendor.debian.tracker_tasks.UpdateExcusesTask`.
  1505. """
  1506. def setUp(self):
  1507. self.package_name = SourcePackageName.objects.create(
  1508. name='dummy-package')
  1509. self.package = SourcePackage(
  1510. source_package_name=self.package_name, version='1.0.0')
  1511. self.task = UpdateExcusesTask()
  1512. self.task._get_update_excuses_content = mock.MagicMock()
  1513. def run_task(self):
  1514. self.task.execute()
  1515. def set_update_excuses_content(self, content):
  1516. """
  1517. Sets the stub content of the update_excuses.html that the task will
  1518. have access to.
  1519. """
  1520. self.task._get_update_excuses_content.return_value = iter(
  1521. content.splitlines())
  1522. def set_update_excuses_content_from_file(self, file_name):
  1523. """
  1524. Sets the stub content of the update_excuses.html that the task will
  1525. have access to based on the content of the test file with the given
  1526. name.
  1527. """
  1528. with open(self.get_test_data_path(file_name), 'r') as f:
  1529. content = f.read()
  1530. self.set_update_excuses_content(content)
  1531. def get_action_item_type(self):
  1532. return ActionItemType.objects.get_or_create(
  1533. type_name=UpdateExcusesTask.ACTION_ITEM_TYPE_NAME)[0]
  1534. def test_action_item_created(self):
  1535. """
  1536. Tests that an action item is created when a package has not moved to
  1537. testing after the allocated period.
  1538. """
  1539. self.set_update_excuses_content_from_file('update_excuses-1.html')
  1540. # Sanity check: no action items currently
  1541. self.assertEqual(0, ActionItem.objects.count())
  1542. expected_data = {
  1543. 'age': '20',
  1544. 'limit': '10',
  1545. }
  1546. self.run_task()
  1547. # An action item is created
  1548. self.assertEqual(1, ActionItem.objects.count())
  1549. # Correct type
  1550. item = ActionItem.objects.all()[0]
  1551. self.assertEqual(
  1552. item.item_type.type_name,
  1553. UpdateExcusesTask.ACTION_ITEM_TYPE_NAME)
  1554. # Correct extra data
  1555. self.assertDictEqual(item.extra_data, expected_data)
  1556. # Correct template used
  1557. self.assertEqual(
  1558. item.full_description_template,
  1559. UpdateExcusesTask.ITEM_FULL_DESCRIPTION_TEMPLATE)
  1560. def test_action_item_not_created(self):
  1561. """
  1562. Tests that an action item is not created when the allocated time period
  1563. has not yet passed.
  1564. """
  1565. self.set_update_excuses_content_from_file('update_excuses-2.html')
  1566. # Sanity check: no action items currently
  1567. self.assertEqual(0, ActionItem.objects.count())
  1568. self.run_task()
  1569. # Still no action items
  1570. self.assertEqual(0, ActionItem.objects.count())
  1571. def test_action_item_removed(self):
  1572. """
  1573. Tests that an already existing action item is removed when the package
  1574. is no longer problematic.
  1575. """
  1576. # Create an item for the package prior to running the task
  1577. ActionItem.objects.create(
  1578. package=self.package_name,
  1579. item_type=self.get_action_item_type(),
  1580. short_description="Desc")
  1581. self.set_update_excuses_content_from_file('update_excuses-2.html')
  1582. self.run_task()
  1583. # The action item is removed.
  1584. self.assertEqual(0, ActionItem.objects.count())
  1585. def test_action_item_updated(self):
  1586. """
  1587. Tests that an already existing action item's extra data is updated.
  1588. """
  1589. ActionItem.objects.create(
  1590. package=self.package_name,
  1591. item_type=self.get_action_item_type(),
  1592. short_description="Desc")
  1593. self.set_update_excuses_content_from_file('update_excuses-1.html')
  1594. expected_data = {
  1595. 'age': '20',
  1596. 'limit': '10',
  1597. }
  1598. self.run_task()
  1599. # Still just one item
  1600. self.assertEqual(1, ActionItem.objects.count())
  1601. # Extra data updated?
  1602. item = ActionItem.objects.all()[0]
  1603. self.assertDictEqual(expected_data, item.extra_data)
  1604. class UpdateBuildLogCheckStatsActionItemTests(TestCase):
  1605. """
  1606. Tests that :class:`distro_tracker.core.models.ActionItem` instances are
  1607. correctly created when running the
  1608. :class:`distro_tracker.vendor.debian.tracker_tasks.UpdateBuildLogCheckStats`
  1609. task.
  1610. """
  1611. def setUp(self):
  1612. self.package_name = SourcePackageName.objects.create(
  1613. name='dummy-package')
  1614. self.package = SourcePackage(
  1615. source_package_name=self.package_name, version='1.0.0')
  1616. self.task = UpdateBuildLogCheckStats()
  1617. self.task._get_buildd_content = mock.MagicMock()
  1618. def set_buildd_content(self, content):
  1619. """
  1620. Sets the stub value for buildd data which the task will see once it
  1621. runs.
  1622. """
  1623. self.task._get_buildd_content.return_value = content
  1624. def run_task(self):
  1625. self.task.execute()
  1626. def get_action_item_type(self):
  1627. return ActionItemType.objects.get_or_create(
  1628. type_name=UpdateBuildLogCheckStats.ACTION_ITEM_TYPE_NAME)[0]
  1629. def test_action_item_created(self):
  1630. """
  1631. Tests that a new action item is created when a package has errors
  1632. or warnings.
  1633. """
  1634. expected_data = {
  1635. 'errors': 1,
  1636. 'warnings': 2,
  1637. }
  1638. self.set_buildd_content("dummy-package|1|2|0|0")
  1639. # Sanity check: no action item
  1640. self.assertEqual(0, ActionItem.objects.count())
  1641. self.run_task()
  1642. # Action item created
  1643. self.assertEqual(1, ActionItem.objects.count())
  1644. item = ActionItem.objects.all()[0]
  1645. self.assertEqual(
  1646. item.item_type.type_name,
  1647. self.get_action_item_type().type_name)
  1648. # Contains the correct extra data
  1649. self.assertDictEqual(expected_data, item.extra_data)
  1650. # The severity is high since it contains both errors and warnings
  1651. self.assertEqual('high', item.get_severity_display())
  1652. # Full description template correct
  1653. self.assertEqual(
  1654. item.full_description_template,
  1655. UpdateBuildLogCheckStats.ITEM_FULL_DESCRIPTION_TEMPLATE)
  1656. def test_action_item_warning_low_severity(self):
  1657. """
  1658. Tests that action items have low severity if the package only has
  1659. warnings.
  1660. """
  1661. self.set_buildd_content("dummy-package|0|1|0|0")
  1662. self.run_task()
  1663. self.assertEqual(1, ActionItem.objects.count())
  1664. item = ActionItem.objects.all()[0]
  1665. self.assertEqual('low', item.get_severity_display())
  1666. def test_action_item_error_high_severity(self):
  1667. """
  1668. Tests that action items have high severity if the package has only
  1669. errors.
  1670. """
  1671. self.set_buildd_content("dummy-package|1|0|0|0")
  1672. self.run_task()
  1673. self.assertEqual(1, ActionItem.objects.count())
  1674. item = ActionItem.objects.all()[0]
  1675. self.assertEqual('high', item.get_severity_display())
  1676. def test_action_item_not_created(self):
  1677. """
  1678. Tests that a new action item is not created when the package does not
  1679. have any errors or warnings.
  1680. """
  1681. # Package has some buildd stats, but no warnings or errors
  1682. self.set_buildd_content("dummy-package|0|0|1|1")
  1683. # Sanity check: no action item
  1684. self.assertEqual(0, ActionItem.objects.count())
  1685. self.run_task()
  1686. # No item created.
  1687. self.assertEqual(0, ActionItem.objects.count())
  1688. def test_action_item_updated(self):
  1689. """
  1690. Tests that an already existing action item is updated when the task
  1691. runs.
  1692. """
  1693. # Create an action item which exists before that task is run
  1694. old_item = ActionItem.objects.create(
  1695. package=self.package_name,
  1696. item_type=self.get_action_item_type(),
  1697. short_description="Desc")
  1698. old_timestamp = old_item.last_updated_timestamp
  1699. expected_data = {
  1700. 'errors': 1,
  1701. 'warnings': 2,
  1702. }
  1703. self.set_buildd_content("dummy-package|1|2|1|1")
  1704. self.run_task()
  1705. # Stll just one action item
  1706. self.assertEqual(1, ActionItem.objects.count())
  1707. # The extra data has been updated?
  1708. item = ActionItem.objects.all()[0]
  1709. self.assertEqual(expected_data, item.extra_data)
  1710. # The time stamp is updated?
  1711. self.assertNotEqual(old_timestamp, item.last_updated_timestamp)
  1712. def test_action_item_not_updated(self):
  1713. """
  1714. Tests that an already existing action item is unchanged if the new data
  1715. does not differ from the already stored data.
  1716. """
  1717. # Create an action item which exists before that task is run
  1718. expected_data = {
  1719. 'errors': 1,
  1720. 'warnings': 2,
  1721. }
  1722. old_item = ActionItem.objects.create(
  1723. package=self.package_name,
  1724. item_type=self.get_action_item_type(),
  1725. short_description="Desc",
  1726. extra_data=expected_data)
  1727. old_timestamp = old_item.last_updated_timestamp
  1728. self.set_buildd_content("dummy-package|1|2|1|1")
  1729. self.run_task()
  1730. # Stll just one action item
  1731. self.assertEqual(1, ActionItem.objects.count())
  1732. item = ActionItem.objects.all()[0]
  1733. # The item is unchanged
  1734. self.assertEqual(old_timestamp, item.last_updated_timestamp)
  1735. def test_action_item_removed(self):
  1736. """
  1737. Tests that an already existing action item is removed when the package
  1738. no longer has any warnings or errors (but still has buildd stats).
  1739. """
  1740. ActionItem.objects.create(
  1741. package=self.package_name,
  1742. item_type=self.get_action_item_type(),
  1743. short_description="Desc")
  1744. self.set_buildd_content("dummy-package|0|0|1|1")
  1745. self.run_task()
  1746. # No longer has any action items.
  1747. self.assertEqual(0, ActionItem.objects.count())
  1748. def test_action_item_removed_all_stats(self):
  1749. """
  1750. Tests that an already existing action item is removed when the package
  1751. no longer has any buildd stats.
  1752. """
  1753. ActionItem.objects.create(
  1754. package=self.package_name,
  1755. item_type=self.get_action_item_type(),
  1756. short_description="Desc")
  1757. self.set_buildd_content("other-package|0|1|1|1")
  1758. self.run_task()
  1759. # No longer has any action items.
  1760. self.assertEqual(0, ActionItem.objects.count())
  1761. def test_action_item_multiple_packages(self):
  1762. """
  1763. Tests that an action item is correctly created for multple packages
  1764. found in the buildd response.
  1765. """
  1766. other_package = SourcePackageName.objects.create(name='other-package')
  1767. self.set_buildd_content(
  1768. "other-package|0|1|1|1\n"
  1769. "dummy-package|1|1|0|0")
  1770. self.run_task()
  1771. # Both packages have an action item
  1772. self.assertEqual(1, other_package.action_items.count())
  1773. self.assertEqual(1, self.package_name.action_items.count())
  1774. class DebianWatchFileScannerUpdateTests(TestCase):
  1775. """
  1776. Tests that :class:`distro_tracker.core.models.ActionItem` instances are
  1777. correctly created when running the
  1778. :class:`distro_tracker.vendor.debian.tracker_tasks.UpdateBuildLogCheckStats`
  1779. task.
  1780. """
  1781. def setUp(self):
  1782. self.package = SourcePackageName.objects.create(name='dummy-package')
  1783. self.task = DebianWatchFileScannerUpdate()
  1784. # Stub the data providing methods: no content by default
  1785. self.task._get_upstream_status_content = mock.MagicMock(
  1786. return_value=b'')
  1787. def run_task(self):
  1788. self.task.execute()
  1789. def set_upstream_status_content(self, content):
  1790. """
  1791. Sets the stub content returned to the task as UDD DEHS data.
  1792. :param content: A list of dicts of information returned by UDD. The
  1793. content given as a response to the task will be the YAML encoded
  1794. representation of this list.
  1795. """
  1796. self.task._get_upstream_status_content.return_value = json.dumps(
  1797. content).encode('utf-8')
  1798. def get_item_type(self, type_name):
  1799. """
  1800. Helper method returning a
  1801. :class:`distro_tracker.core.models.ActionItemType` instance with the
  1802. given type name.
  1803. """
  1804. return ActionItemType.objects.get_or_create(type_name=type_name)[0]
  1805. def test_new_upstream_version_item_created(self):
  1806. """
  1807. Tests that a new upstream version item is created when a package has
  1808. a newer upstream version according to DEHS data retrieved from UDD.
  1809. """
  1810. version = '2.0.0'
  1811. url = 'http://some.url.here'
  1812. dehs = [
  1813. {
  1814. 'package': self.package.name,
  1815. 'status': 'Newer version available',
  1816. 'upstream-url': url,
  1817. 'upstream-version': version,
  1818. }
  1819. ]
  1820. self.set_upstream_status_content(dehs)
  1821. # Sanity check: no action items
  1822. self.assertEqual(0, ActionItem.objects.count())
  1823. self.run_task()
  1824. # Action item created.
  1825. self.assertEqual(1, ActionItem.objects.count())
  1826. # Action item correct type
  1827. item = ActionItem.objects.all()[0]
  1828. self.assertEqual(
  1829. 'new-upstream-version',
  1830. item.item_type.type_name)
  1831. # Correct full description template
  1832. self.assertEqual(
  1833. DebianWatchFileScannerUpdate.ACTION_ITEM_TEMPLATES
  1834. ['new-upstream-version'], item.full_description_template)
  1835. # Correct extra data
  1836. expected_data = {
  1837. 'upstream_version': version,
  1838. 'upstream_url': url,
  1839. }
  1840. self.assertDictEqual(expected_data, item.extra_data)
  1841. # High severity item
  1842. self.assertEqual('high', item.get_severity_display())
  1843. def test_new_upstream_version_item_removed(self):
  1844. """
  1845. Tests that a new upstream version item is removed when a package no
  1846. longer has a newer upstream version.
  1847. """
  1848. # Make sure the package previously had an action item.
  1849. item_type = self.get_item_type('new-upstream-version')
  1850. ActionItem.objects.create(
  1851. package=self.package,
  1852. item_type=item_type,
  1853. short_description='Desc')
  1854. dehs = []
  1855. self.set_upstream_status_content(dehs)
  1856. self.run_task()
  1857. # Action item removed
  1858. self.assertEqual(0, ActionItem.objects.count())
  1859. def test_new_upstream_version_item_updated(self):
  1860. """
  1861. Tests that a new upstream version action item is updated when there is
  1862. newer data available for the package.
  1863. """
  1864. item_type = self.get_item_type('new-upstream-version')
  1865. ActionItem.objects.create(
  1866. package=self.package,
  1867. item_type=item_type,
  1868. short_description='Desc')
  1869. url = 'http://some.url'
  1870. version = '2.0.0'
  1871. dehs = [
  1872. {
  1873. 'package': self.package.name,
  1874. 'status': 'Newer version available',
  1875. 'upstream-url': url,
  1876. 'upstream-version': version,
  1877. }
  1878. ]
  1879. self.set_upstream_status_content(dehs)
  1880. self.run_task()
  1881. # Still the one action item
  1882. self.assertEqual(1, ActionItem.objects.count())
  1883. # Extra data updated
  1884. item = ActionItem.objects.all()[0]
  1885. expected_data = {
  1886. 'upstream_url': url,
  1887. 'upstream_version': version,
  1888. }
  1889. self.assertEqual(expected_data, item.extra_data)
  1890. def test_watch_failure_item_created(self):
  1891. """
  1892. Tests that a ``watch-failure`` action item is created when the package
  1893. contains a watch failure as indicated by DEHS data returned by UDD.
  1894. """
  1895. version = '2.0.0'
  1896. url = 'http://some.url.here'
  1897. warning = 'Some warning goes here...'
  1898. dehs = [
  1899. {
  1900. 'package': self.package.name,
  1901. 'status': 'up to date',
  1902. 'upstream-url': url,
  1903. 'upstream-version': version,
  1904. 'warnings': warning,
  1905. }
  1906. ]
  1907. self.set_upstream_status_content(dehs)
  1908. # Sanity check: no action items
  1909. self.assertEqual(0, ActionItem.objects.count())
  1910. self.run_task()
  1911. # Action item created.
  1912. self.assertEqual(1, ActionItem.objects.count())
  1913. # Action item correct type
  1914. item = ActionItem.objects.all()[0]
  1915. self.assertEqual(
  1916. 'watch-failure',
  1917. item.item_type.type_name)
  1918. # Correct full description template
  1919. self.assertEqual(
  1920. DebianWatchFileScannerUpdate.ACTION_ITEM_TEMPLATES['watch-failure'],
  1921. item.full_description_template)
  1922. # Correct extra data
  1923. expected_data = {
  1924. 'warning': warning,
  1925. }
  1926. self.assertDictEqual(expected_data, item.extra_data)
  1927. # High severity item
  1928. self.assertEqual('high', item.get_severity_display())
  1929. def test_watch_failure_item_removed(self):
  1930. """
  1931. Tests that a ``watch-failure`` item is removed when a package no longer
  1932. has the issue.
  1933. """
  1934. # Make sure the package previously had an action item.
  1935. item_type = self.get_item_type('watch-failure')
  1936. ActionItem.objects.create(
  1937. package=self.package,
  1938. item_type=item_type,
  1939. short_description='Desc')
  1940. dehs = []
  1941. self.set_upstream_status_content(dehs)
  1942. self.run_task()
  1943. # Action item removed
  1944. self.assertEqual(0, ActionItem.objects.count())
  1945. def test_watch_failure_item_updated(self):
  1946. """
  1947. Tests that a ``watch-failure`` action item is updated when there is
  1948. newer data available for the package.
  1949. """
  1950. item_type = self.get_item_type('watch-failure')
  1951. ActionItem.objects.create(
  1952. package=self.package,
  1953. item_type=item_type,
  1954. short_description='Desc',
  1955. extra_data={
  1956. 'warning': 'Old warning',
  1957. })
  1958. version = '2.0.0'
  1959. url = 'http://some.url.here'
  1960. warning = 'Some warning goes here...'
  1961. dehs = [
  1962. {
  1963. 'package': self.package.name,
  1964. 'status': 'up to date',
  1965. 'upstream-url': url,
  1966. 'upstream-version': version,
  1967. 'warnings': warning,
  1968. }
  1969. ]
  1970. self.set_upstream_status_content(dehs)
  1971. self.run_task()
  1972. # Still the one action item
  1973. self.assertEqual(1, ActionItem.objects.count())
  1974. # Extra data updated
  1975. item = ActionItem.objects.all()[0]
  1976. expected_data = {
  1977. 'warning': warning,
  1978. }
  1979. self.assertEqual(expected_data, item.extra_data)
  1980. def test_no_dehs_data(self):
  1981. """
  1982. Tests that when there is no DEHS data at all, no action items are
  1983. created.
  1984. """
  1985. self.run_task()
  1986. self.assertEqual(0, ActionItem.objects.count())
  1987. class UpdateSecurityIssuesTaskTests(TestCase):
  1988. def setUp(self):
  1989. self.package = SourcePackageName.objects.create(name='dummy-package')
  1990. self.task = UpdateSecurityIssuesTask()
  1991. # Stub the data providing methods: no content by default
  1992. self.task._get_issues_content = mock.MagicMock(return_value='')
  1993. def load_test_json(self, key):
  1994. datafn = 'security-tracker-{}.json'.format(key)
  1995. with open(self.get_test_data_path(datafn), 'r') as f:
  1996. content = json.load(f)
  1997. return content
  1998. def mock_json_data(self, key=None, content={}):
  1999. if key:
  2000. content = self.load_test_json(key)
  2001. self.task._get_issues_content = mock.MagicMock(return_value=content)
  2002. return content
  2003. def run_task(self):
  2004. self.task.execute()
  2005. def get_item_type(self):
  2006. return ActionItemType.objects.get_or_create(
  2007. type_name='debian-security-issue')[0]
  2008. def test_get_issues_summary_with_eol(self):
  2009. data = self.load_test_json('eol')['dummy-package']
  2010. stats = self.task.get_issues_summary(data)
  2011. self.assertEqual(stats['jessie']['open'], 0)
  2012. self.assertEqual(stats['sid']['open'], 1)
  2013. def test_get_issues_summary_with_unimportant(self):
  2014. data = self.load_test_json('unimportant')['dummy-package']
  2015. stats = self.task.get_issues_summary(data)
  2016. self.assertEqual(stats['jessie']['open'], 0)
  2017. self.assertEqual(stats['sid']['open'], 0)
  2018. self.assertEqual(stats['jessie']['nodsa'], 0)
  2019. self.assertEqual(stats['sid']['nodsa'], 0)
  2020. self.assertEqual(stats['jessie']['unimportant'], 1)
  2021. self.assertEqual(stats['sid']['unimportant'], 0)
  2022. def test_get_issues_summary_with_nodsa(self):
  2023. data = self.load_test_json('nodsa')['dummy-package']
  2024. stats = self.task.get_issues_summary(data)
  2025. self.assertEqual(stats['jessie']['open'], 0)
  2026. self.assertEqual(stats['sid']['open'], 0)
  2027. self.assertEqual(stats['jessie']['nodsa'], 1)
  2028. self.assertEqual(stats['sid']['nodsa'], 0)
  2029. self.assertEqual(stats['jessie']['unimportant'], 0)
  2030. self.assertEqual(stats['sid']['unimportant'], 0)
  2031. def test_get_issues_summary_with_open(self):
  2032. data = self.load_test_json('open')['dummy-package']
  2033. stats = self.task.get_issues_summary(data)
  2034. self.assertEqual(stats['jessie']['open'], 2)
  2035. self.assertEqual(stats['jessie']['nodsa'], 1)
  2036. self.assertEqual(stats['jessie']['unimportant'], 0)
  2037. self.assertEqual(stats['stretch']['open'], 0)
  2038. self.assertEqual(stats['stretch']['nodsa'], 0)
  2039. self.assertEqual(stats['stretch']['unimportant'], 1)
  2040. self.assertEqual(stats['sid']['open'], 2)
  2041. self.assertEqual(stats['sid']['nodsa'], 0)
  2042. self.assertEqual(stats['sid']['unimportant'], 0)
  2043. def test_get_issues_summary_has_details(self):
  2044. data = self.load_test_json('open')['dummy-package']
  2045. stats = self.task.get_issues_summary(data)
  2046. self.assertDictEqual(
  2047. stats['jessie']['open_details'],
  2048. {
  2049. 'CVE-2015-0234': 'Description of CVE-2015-0234',
  2050. 'CVE-2015-0235': '',
  2051. }
  2052. )
  2053. self.assertDictEqual(
  2054. stats['jessie']['nodsa_details'],
  2055. {
  2056. 'CVE-2015-0233': 'Description of CVE-2015-0233',
  2057. }
  2058. )
  2059. def test_get_issues_stats(self):
  2060. content = self.mock_json_data('open')
  2061. stats = self.task.get_issues_stats(content)
  2062. self.assertTrue(stats['dummy-package']['jessie']['open'], 2)
  2063. self.assertTrue(stats['dummy-package']['jessie']['nodsa'], 1)
  2064. def test_get_data_checksum(self):
  2065. checksum = self.task.get_data_checksum({})
  2066. self.assertEqual(checksum, '99914b932bd37a50b983c5e7c90ae93b')
  2067. def test_execute_create_data(self):
  2068. self.mock_json_data('open')
  2069. self.run_task()
  2070. data = self.package.packageextractedinfo_set.get(
  2071. key='debian-security').value
  2072. self.assertEqual(data['stats']['jessie']['open'], 2)
  2073. self.assertEqual(data['stats']['jessie']['nodsa'], 1)
  2074. def test_execute_drop_data(self):
  2075. pkg = SourcePackageName.objects.create(name='pkg')
  2076. pkg.packageextractedinfo_set.create(key='debian-security', value={})
  2077. self.mock_json_data('open')
  2078. self.assertEqual(
  2079. pkg.packageextractedinfo_set.filter(key='debian-security').count(),
  2080. 1)
  2081. self.run_task()
  2082. self.assertEqual(
  2083. pkg.packageextractedinfo_set.filter(key='debian-security').count(),
  2084. 0)
  2085. def test_execute_update_data(self):
  2086. self.package.packageextractedinfo_set.create(
  2087. key='debian-security',
  2088. value={
  2089. 'details': {},
  2090. 'stats': {},
  2091. 'checksum': '99914b932bd37a50b983c5e7c90ae93b',
  2092. })
  2093. content = self.mock_json_data('open')
  2094. self.run_task()
  2095. after = self.package.packageextractedinfo_set.get(key='debian-security')
  2096. self.assertNotEqual(after.value['checksum'],
  2097. '99914b932bd37a50b983c5e7c90ae93b')
  2098. self.maxDiff = None
  2099. self.assertDictEqual(
  2100. after.value,
  2101. self.task.generate_package_data(content['dummy-package'])
  2102. )
  2103. def test_execute_update_data_skipped(self):
  2104. # Inject an inconsistent initial value that would be overwritten
  2105. # in case of update
  2106. initial_value = {
  2107. 'details': {
  2108. 'test_key': 'test_value'
  2109. },
  2110. 'stats': {
  2111. 'test_key': 'test_value'
  2112. },
  2113. # This checksum is for details={} (empty dict)
  2114. 'checksum': '99914b932bd37a50b983c5e7c90ae93b',
  2115. }
  2116. self.package.packageextractedinfo_set.create(
  2117. key='debian-security', value=initial_value)
  2118. # Ensure the data retrieved is empty for the package we test
  2119. content = {
  2120. 'dummy-package': {}
  2121. }
  2122. self.mock_json_data(content=content)
  2123. self.run_task()
  2124. # Ensure that we still have the initial data and that it has not
  2125. # been updated
  2126. after = self.package.packageextractedinfo_set.get(key='debian-security')
  2127. self.assertDictEqual(after.value, initial_value)
  2128. def test_update_action_item(self):
  2129. action_item = ActionItem(extra_data={'release': 'jessie'},
  2130. package=self.package)
  2131. # First case, normal issue
  2132. data = self.load_test_json('open')['dummy-package']
  2133. stats = self.task.get_issues_summary(data)['jessie']
  2134. self.task.update_action_item(stats, action_item)
  2135. self.assertEqual(action_item.severity, ActionItem.SEVERITY_HIGH)
  2136. self.assertIn('security issues</a> in jessie',
  2137. action_item.short_description)
  2138. self.assertEqual(action_item.extra_data['security_issues_count'], 3)
  2139. # Second case, nodsa issue only
  2140. data = self.load_test_json('nodsa')['dummy-package']
  2141. stats = self.task.get_issues_summary(data)['jessie']
  2142. self.task.update_action_item(stats, action_item)
  2143. self.assertEqual(action_item.severity, ActionItem.SEVERITY_LOW)
  2144. self.assertIn('ignored security issue</a> in jessie',
  2145. action_item.short_description)
  2146. self.assertEqual(action_item.extra_data['security_issues_count'], 1)
  2147. def test_action_item_created(self):
  2148. """
  2149. Tests that an action item is created when a package has security
  2150. issues.
  2151. """
  2152. self.mock_json_data('open')
  2153. self.assertEqual(0, ActionItem.objects.count())
  2154. self.run_task()
  2155. self.assertEqual(2, ActionItem.objects.count())
  2156. for item in ActionItem.objects.all():
  2157. self.assertTrue(item.item_type.type_name.startswith(
  2158. 'debian-security-issue-in'))
  2159. self.assertTrue('release' in item.extra_data)
  2160. self.assertTrue('security_issues_count' in item.extra_data)
  2161. def test_action_item_removed(self):
  2162. """
  2163. Tests that an action item is removed when a package no longer has
  2164. security issues.
  2165. """
  2166. self.mock_json_data('open')
  2167. self.run_task()
  2168. self.assertTrue(ActionItem.objects.count() > 0)
  2169. self.mock_json_data('unimportant')
  2170. self.run_task()
  2171. # Removed the action item
  2172. self.assertEqual(0, ActionItem.objects.count())
  2173. def test_action_item_updated(self):
  2174. """
  2175. Tests that an action item is updated when there are no package security
  2176. issue stats.
  2177. """
  2178. self.mock_json_data('nodsa')
  2179. self.run_task()
  2180. ai = ActionItem.objects.get(
  2181. item_type__type_name='debian-security-issue-in-jessie')
  2182. self.assertEqual(ai.extra_data['security_issues_count'], 1)
  2183. self.assertEqual(ai.severity, ActionItem.SEVERITY_LOW)
  2184. self.assertIn("1 ignored security issue", ai.short_description)
  2185. self.mock_json_data('open')
  2186. self.run_task()
  2187. ai = ActionItem.objects.get(id=ai.id)
  2188. self.assertEqual(ai.extra_data['security_issues_count'], 3)
  2189. self.assertEqual(ai.severity, ActionItem.SEVERITY_HIGH)
  2190. self.assertIn("3 security issues", ai.short_description)
  2191. def test_get_template_action_item(self):
  2192. self.mock_json_data('nodsa')
  2193. self.run_task()
  2194. ai = ActionItem.objects.get(
  2195. item_type__type_name='debian-security-issue-in-jessie')
  2196. response = self.client.get(ai.get_absolute_url())
  2197. self.assertTemplateUsed(response,
  2198. 'debian/security-issue-action-item.html')
  2199. class CodeSearchLinksTest(TestCase):
  2200. """
  2201. Tests that the code search links are shown in the package page.
  2202. """
  2203. def setUp(self):
  2204. self.package_name = SourcePackageName.objects.create(name='dummy')
  2205. self.package = SourcePackage.objects.create(
  2206. source_package_name=self.package_name,
  2207. version='1.0.0')
  2208. self.stable = Repository.objects.create(
  2209. name='Debian Stable', codename='wheezy', suite='stable',
  2210. shorthand='stable')
  2211. self.unstable = Repository.objects.create(
  2212. name='Debian Unstable', codename='sid', suite='unstable',
  2213. shorthand='unstable')
  2214. def get_package_page_response(self, package_name):
  2215. package_page_url = reverse('dtracker-package-page', kwargs={
  2216. 'package_name': package_name,
  2217. })
  2218. return self.client.get(package_page_url)
  2219. def browse_link_in_content(self, content):
  2220. html = soup(content, 'html.parser')
  2221. for a_tag in html.findAll('a', {'href': True}):
  2222. if a_tag['href'].startswith('https://sources.debian.net'):
  2223. return True
  2224. return False
  2225. def search_form_in_content(self, content):
  2226. html = soup(content, 'html.parser')
  2227. return bool(html.find('form', {'class': 'code-search-form'}))
  2228. def test_package_in_stable(self):
  2229. """
  2230. Tests that only the browse source code link appears when the package is
  2231. only in stable.
  2232. """
  2233. # Add the package to stable
  2234. self.stable.add_source_package(self.package)
  2235. response = self.get_package_page_response(self.package.name)
  2236. self.assertTrue(self.browse_link_in_content(response.content))
  2237. self.assertFalse(self.search_form_in_content(response.content))
  2238. def test_package_not_in_allowed_repository(self):
  2239. """
  2240. Tests that no links are added when the package is not found in one of
  2241. the allowed repositories
  2242. (:attr:`distro_tracker.vendor.debian.tracker_panels.SourceCodeSearchLinks.ALLOWED_REPOSITORIES`)
  2243. """
  2244. other_repository = Repository.objects.create(name='some-other-repo')
  2245. other_repository.add_source_package(self.package)
  2246. response = self.get_package_page_response(self.package.name)
  2247. self.assertFalse(self.browse_link_in_content(response.content))
  2248. self.assertFalse(self.search_form_in_content(response.content))
  2249. def test_package_in_unstable(self):
  2250. """
  2251. Tests that the search form is shown in addition to the browse source
  2252. link if the package is found in unstable.
  2253. """
  2254. self.unstable.add_source_package(self.package)
  2255. response = self.get_package_page_response(self.package.name)
  2256. response_content = response.content.decode('utf-8')
  2257. self.assertTrue(self.browse_link_in_content(response_content))
  2258. self.assertTrue(self.search_form_in_content(response_content))
  2259. def test_pseudo_package(self):
  2260. """
  2261. Tests that neither link is shown when the package is a pseudo package,
  2262. instead of a source package.
  2263. """
  2264. pseudo_package = PseudoPackageName.objects.create(name='somepackage')
  2265. response = self.get_package_page_response(pseudo_package.name)
  2266. response_content = response.content.decode('utf-8')
  2267. self.assertFalse(self.browse_link_in_content(response_content))
  2268. self.assertFalse(self.search_form_in_content(response_content))
  2269. def test_code_search_view_missing_query_parameter(self):
  2270. """Test codesearch view with missing query parameter"""
  2271. # missing query parameter
  2272. response = self.client.get(reverse('dtracker-code-search'),
  2273. {'package': self.package.name})
  2274. self.assertEqual(response.status_code, 400)
  2275. self.assertIn('Both package and query are required parameters',
  2276. response.content.decode('utf-8'))
  2277. def test_code_search_view_missing_package_parameter(self):
  2278. """Test codesearch view with missing package parameter"""
  2279. response = self.client.get(reverse('dtracker-code-search'),
  2280. {'query': 'def'})
  2281. self.assertEqual(response.status_code, 400)
  2282. def test_code_search_view_empty_query(self):
  2283. """Test codesearch view with empty query"""
  2284. response = self.client.get(reverse('dtracker-code-search'),
  2285. {'package': self.package.name,
  2286. 'query': ''})
  2287. self.assertEqual(response.status_code, 400)
  2288. self.assertIn('Empty query is not allowed',
  2289. response.content.decode('utf-8'))
  2290. def test_code_search_view_redirect_simple(self):
  2291. """Test codesearch view redirects properly"""
  2292. response = self.client.get(reverse('dtracker-code-search'),
  2293. {'package': self.package.name,
  2294. 'query': 'def'})
  2295. self.assertEqual(response.status_code, 302)
  2296. self.assertIn(CodeSearchView.BASE_URL, response['Location'])
  2297. def test_code_search_view_urlencode_where_needed(self):
  2298. """Test codesearch view urlencode stuff"""
  2299. response = self.client.get(reverse('dtracker-code-search'),
  2300. {'package': 'g++',
  2301. 'query': 'bépo'})
  2302. self.assertEqual(response.status_code, 302)
  2303. self.assertIn("q=b%C3%A9po+package%3Ag%2B%2B", response['Location'])
  2304. class PopconLinkTest(TestCase):
  2305. """
  2306. Tests that the popcon link is added to source package pages.
  2307. """
  2308. def get_package_page_response(self, package_name):
  2309. package_page_url = reverse('dtracker-package-page', kwargs={
  2310. 'package_name': package_name,
  2311. })
  2312. return self.client.get(package_page_url)
  2313. def test_source_package(self):
  2314. package_name = SourcePackageName.objects.create(name='dummy')
  2315. package = SourcePackage.objects.create(
  2316. source_package_name=package_name,
  2317. version='1.0.0')
  2318. response = self.get_package_page_response(package.name)
  2319. response_content = response.content.decode('utf8')
  2320. self.assertIn('popcon', response_content)
  2321. def test_pseudo_package(self):
  2322. package = PseudoPackageName.objects.create(name='somepackage')
  2323. response = self.get_package_page_response(package.name)
  2324. response_content = response.content.decode('utf-8')
  2325. self.assertNotIn('popcon', response_content)
  2326. class DebtagsLinkTest(TestCase):
  2327. """
  2328. Tests that the debtags link is added to source package pages.
  2329. """
  2330. def get_package_page_response(self, package_name):
  2331. package_page_url = reverse('dtracker-package-page', kwargs={
  2332. 'package_name': package_name,
  2333. })
  2334. return self.client.get(package_page_url)
  2335. def test_source_package(self):
  2336. package_name = SourcePackageName.objects.create(name='dummy')
  2337. package = SourcePackage.objects.create(
  2338. source_package_name=package_name,
  2339. version='1.0.0')
  2340. PackageExtractedInfo.objects.create(
  2341. package=package.source_package_name,
  2342. key='general',
  2343. value={
  2344. 'name': 'dummy',
  2345. 'maintainer': {
  2346. 'email': 'hertzog@debian.org',
  2347. }
  2348. }
  2349. )
  2350. response = self.get_package_page_response(package.name)
  2351. response_content = response.content.decode('utf8')
  2352. self.assertIn('edit tags', response_content)
  2353. def test_pseudo_package(self):
  2354. package = PseudoPackageName.objects.create(name='somepackage')
  2355. response = self.get_package_page_response(package.name)
  2356. response_content = response.content.decode('utf-8')
  2357. self.assertNotIn('edit tags', response_content)
  2358. class ScreenshotsLinkTest(TestCase):
  2359. """
  2360. Tests that the screenshots link is added to source package pages.
  2361. """
  2362. def setUp(self):
  2363. self.package_name = SourcePackageName.objects.create(name='dummy')
  2364. self.package = SourcePackage.objects.create(
  2365. source_package_name=self.package_name,
  2366. version='1.0.0')
  2367. PackageExtractedInfo.objects.create(
  2368. package=self.package.source_package_name,
  2369. key='screenshots',
  2370. value={'screenshots': 'true'}
  2371. )
  2372. def get_package_page_response(self, package_name):
  2373. package_page_url = reverse('dtracker-package-page', kwargs={
  2374. 'package_name': package_name,
  2375. })
  2376. return self.client.get(package_page_url)
  2377. def test_source_package_with_screenshot(self):
  2378. response = self.get_package_page_response(self.package.name)
  2379. response_content = response.content.decode('utf8')
  2380. self.assertIn('screenshots', response_content)
  2381. def test_source_package_without_screenshot(self):
  2382. package_name = SourcePackageName.objects.create(name='other')
  2383. package = SourcePackage.objects.create(
  2384. source_package_name=package_name,
  2385. version='1.0.0')
  2386. response = self.get_package_page_response(package.name)
  2387. response_content = response.content.decode('utf8')
  2388. self.assertNotIn('screenshots', response_content)
  2389. def test_pseudo_package(self):
  2390. package = PseudoPackageName.objects.create(name='somepackage')
  2391. response = self.get_package_page_response(package.name)
  2392. response_content = response.content.decode('utf-8')
  2393. self.assertNotIn('screenshots', response_content)
  2394. class UpdatePiupartsTaskTests(TestCase):
  2395. suites = []
  2396. @staticmethod
  2397. def stub_get_piuparts_content(suite, stub_data):
  2398. return stub_data.get(suite, None)
  2399. def setUp(self):
  2400. self.package = SourcePackageName.objects.create(name='dummy-package')
  2401. self.task = UpdatePiuPartsTask()
  2402. # Stub the data providing methods
  2403. self.return_content = {}
  2404. self.task._get_piuparts_content = mock.MagicMock(
  2405. side_effect=curry(
  2406. UpdatePiupartsTaskTests.stub_get_piuparts_content,
  2407. stub_data=self.return_content))
  2408. # Clear the actual list of suites
  2409. self.suites[:] = []
  2410. def run_task(self):
  2411. self.task.execute()
  2412. def get_action_item_type(self):
  2413. return ActionItemType.objects.get_or_create(
  2414. type_name=UpdatePiuPartsTask.ACTION_ITEM_TYPE_NAME)[0]
  2415. def set_suites(self, suites):
  2416. """
  2417. Sets the list of suites which the task should use.
  2418. """
  2419. for suite in suites:
  2420. self.suites.append(suite)
  2421. def set_piuparts_content(self, suite, fail_packages, pass_packages=()):
  2422. """
  2423. Sets the given list of packages as a stub value which is returned to
  2424. the task for the given suite.
  2425. """
  2426. content = '\n'.join('{}: fail'.format(pkg) for pkg in fail_packages)
  2427. content += '\n'.join('{}: pass'.format(pkg) for pkg in pass_packages)
  2428. self.return_content[suite] = content
  2429. def assert_get_piuparts_called_with(self, suites):
  2430. """
  2431. Asserts that the _get_piuparts_content method was called only with the
  2432. given suites.
  2433. """
  2434. self.assertEqual(
  2435. len(suites),
  2436. len(self.task._get_piuparts_content.mock_calls))
  2437. for suite, mock_call in \
  2438. zip(suites, self.task._get_piuparts_content.call_args_list):
  2439. self.assertEqual(mock.call(suite), mock_call)
  2440. @override_settings(DISTRO_TRACKER_DEBIAN_PIUPARTS_SUITES=suites)
  2441. def test_retrieves_all_suites(self):
  2442. """
  2443. Tests that the task tries to retrieve the data for each of the suites
  2444. given in the
  2445. :data:`distro_tracker.project.local_settings.DISTRO_TRACKER_DEBIAN_PIUPARTS_SUITES`
  2446. setting.
  2447. """
  2448. suites = ['sid', 'jessie']
  2449. self.set_suites(suites)
  2450. self.run_task()
  2451. self.assert_get_piuparts_called_with(suites)
  2452. @override_settings(DISTRO_TRACKER_DEBIAN_PIUPARTS_SUITES=suites)
  2453. def test_action_item_created(self):
  2454. """
  2455. Tests that an action item is created when a source package is found to
  2456. be failing the piuparts test in a single suite.
  2457. """
  2458. packages = [self.package.name]
  2459. suite = 'jessie'
  2460. self.set_suites([suite])
  2461. self.set_piuparts_content(suite, packages)
  2462. self.run_task()
  2463. # Created the action item.
  2464. self.assertEqual(1, ActionItem.objects.count())
  2465. # Correct item type?
  2466. item = ActionItem.objects.all()[0]
  2467. self.assertEqual(
  2468. UpdatePiuPartsTask.ACTION_ITEM_TYPE_NAME,
  2469. item.item_type.type_name)
  2470. # Correct template?
  2471. self.assertEqual(
  2472. UpdatePiuPartsTask.ACTION_ITEM_TEMPLATE,
  2473. item.full_description_template)
  2474. # Correct list of failing suites?
  2475. self.assertEqual([suite], item.extra_data['suites'])
  2476. @override_settings(DISTRO_TRACKER_DEBIAN_PIUPARTS_SUITES=suites)
  2477. def test_action_item_not_created(self):
  2478. """
  2479. Tests that an action item is not created when a source package is found
  2480. to be passing the piuparts test.
  2481. """
  2482. packages = [self.package.name]
  2483. suite = 'jessie'
  2484. self.set_suites([suite])
  2485. self.set_piuparts_content(suite, [], packages)
  2486. self.run_task()
  2487. # No action item created
  2488. self.assertEqual(0, ActionItem.objects.count())
  2489. @override_settings(DISTRO_TRACKER_DEBIAN_PIUPARTS_SUITES=suites)
  2490. def test_action_item_updated(self):
  2491. """
  2492. Tests that an existing action item is updated when there are updated
  2493. piuparts stats for the package.
  2494. """
  2495. # Create an action item: package failing in sid
  2496. ActionItem.objects.create(
  2497. package=self.package,
  2498. item_type=self.get_action_item_type(),
  2499. short_description="Desc",
  2500. extra_data={
  2501. 'suites': ['sid']
  2502. })
  2503. packages = [self.package.name]
  2504. suite = 'jessie'
  2505. self.set_suites([suite])
  2506. self.set_piuparts_content(suite, packages)
  2507. self.run_task()
  2508. # Still only one action item
  2509. self.assertEqual(1, ActionItem.objects.count())
  2510. # Updated a list of suites
  2511. item = ActionItem.objects.all()[0]
  2512. self.assertEqual([suite], item.extra_data['suites'])
  2513. @override_settings(DISTRO_TRACKER_DEBIAN_PIUPARTS_SUITES=suites)
  2514. def test_action_item_multiple_suites(self):
  2515. """
  2516. Tests that an action item contains all suites in which a failure was
  2517. detected.
  2518. """
  2519. packages = [self.package.name]
  2520. # Suites where piuparts is failing
  2521. suites = ['sid', 'jessie']
  2522. # Suites where piuparts is ok
  2523. pass_suites = ['wheezy']
  2524. self.set_suites(suites + pass_suites)
  2525. for suite in suites:
  2526. self.set_piuparts_content(suite, packages)
  2527. for suite in pass_suites:
  2528. self.set_piuparts_content(suite, [], packages)
  2529. self.run_task()
  2530. self.assertEqual(1, ActionItem.objects.count())
  2531. item = ActionItem.objects.all()[0]
  2532. # All the suites found in extra data?
  2533. self.assertEqual(len(suites), len(item.extra_data['suites']))
  2534. for suite in suites:
  2535. self.assertIn(suite, item.extra_data['suites'])
  2536. @override_settings(DISTRO_TRACKER_DEBIAN_PIUPARTS_SUITES=suites)
  2537. def test_action_item_not_updated_when_unchanged(self):
  2538. """
  2539. Tests that an existing action item is not updated when the update
  2540. detects that the stats have not changed.
  2541. """
  2542. # Create an action item: package failing in multiple repositories
  2543. item = ActionItem.objects.create(
  2544. package=self.package,
  2545. item_type=self.get_action_item_type(),
  2546. short_description="Desc",
  2547. extra_data={
  2548. 'suites': ['jessie', 'sid']
  2549. })
  2550. old_timestamp = item.last_updated_timestamp
  2551. packages = [self.package.name]
  2552. # Different order of suites than the one found in the current item
  2553. # should not affect anything
  2554. suites = ['sid', 'jessie']
  2555. self.set_suites(suites)
  2556. for suite in suites:
  2557. self.set_piuparts_content(suite, packages)
  2558. self.run_task()
  2559. # Still only one action item
  2560. self.assertEqual(1, ActionItem.objects.count())
  2561. # Still the same list of suites
  2562. item = ActionItem.objects.all()[0]
  2563. self.assertEqual(['jessie', 'sid'], item.extra_data['suites'])
  2564. # Time stamp unchanged
  2565. self.assertEqual(old_timestamp, item.last_updated_timestamp)
  2566. @override_settings(DISTRO_TRACKER_DEBIAN_PIUPARTS_SUITES=suites)
  2567. def test_action_item_removed(self):
  2568. """
  2569. Tests that an existing action item is removed if the update indicates
  2570. the package is passing piuparts tests in all suites.
  2571. """
  2572. ActionItem.objects.create(
  2573. package=self.package,
  2574. item_type=self.get_action_item_type(),
  2575. short_description="Desc",
  2576. extra_data={
  2577. 'suites': ['jessie', 'sid']
  2578. })
  2579. packages = [self.package.name]
  2580. # Different order of suites than the one found in the current item
  2581. # should not affect anything
  2582. suites = ['sid', 'jessie']
  2583. self.set_suites(suites)
  2584. # Set the package as passing in all suites
  2585. for suite in suites:
  2586. self.set_piuparts_content(suite, [], packages)
  2587. self.run_task()
  2588. # Action item removed?
  2589. self.assertEqual(0, ActionItem.objects.count())
  2590. @override_settings(DISTRO_TRACKER_DEBIAN_PIUPARTS_SUITES=suites)
  2591. def test_action_item_removed_no_stats(self):
  2592. """
  2593. Tests that an existing action item is removed if the update indicates
  2594. the package no longer has any piuparts stats.
  2595. """
  2596. ActionItem.objects.create(
  2597. package=self.package,
  2598. item_type=self.get_action_item_type(),
  2599. short_description="Desc",
  2600. extra_data={
  2601. 'suites': ['jessie', 'sid']
  2602. })
  2603. self.run_task()
  2604. self.assertEqual(0, ActionItem.objects.count())
  2605. class UpdateUbuntuStatsTaskTests(TestCase):
  2606. """
  2607. Tests for the
  2608. :class:`distro_tracker.vendor.debian.tracker_taks.UpdateUbuntuStatsTask`
  2609. task.
  2610. """
  2611. def setUp(self):
  2612. self.package = SourcePackageName.objects.create(name='dummy-package')
  2613. self.task = UpdateUbuntuStatsTask()
  2614. # Stub the data providing method
  2615. self.task._get_versions_content = mock.MagicMock(return_value='')
  2616. self.task._get_bug_stats_content = mock.MagicMock(return_value='')
  2617. self.task._get_ubuntu_patch_diff_content = mock.MagicMock(
  2618. return_value='')
  2619. def set_versions_content(self, versions):
  2620. """
  2621. Sets the stub content for the list of Ubuntu package versions.
  2622. :param versions: A list of (package_name, version) pairs which should
  2623. be found in the response.
  2624. """
  2625. self.task._get_versions_content.return_value = '\n'.join(
  2626. '{pkg} {ver}'.format(pkg=pkg, ver=ver)
  2627. for pkg, ver in versions)
  2628. def set_bugs_content(self, bugs):
  2629. """
  2630. Sets the stub content for the list of Ubuntu package bugs.
  2631. :param bugs: A list of (package_name, bug_count, patch_count) tuples
  2632. which should be found in the response.
  2633. """
  2634. self.task._get_bug_stats_content.return_value = '\n'.join(
  2635. '{pkg}|{cnt}|{merged}'.format(pkg=pkg, cnt=cnt, merged=merged)
  2636. for pkg, cnt, merged in bugs)
  2637. def set_diff_content(self, diffs):
  2638. """
  2639. Sets the stub content for the list of diff URLs.
  2640. :param diffs: A list of (package_name, diff_url) pairs which should be
  2641. found in the response.
  2642. """
  2643. self.task._get_ubuntu_patch_diff_content.return_value = '\n'.join(
  2644. '{pkg} {url}'.format(pkg=pkg, url=url)
  2645. for pkg, url in diffs)
  2646. def run_task(self):
  2647. self.task.execute()
  2648. def test_ubuntu_package_created(self):
  2649. """
  2650. Tests that a new
  2651. :class:`distro_tracker.vendor.debian.models.UbuntuPackage` model
  2652. instance is created if an Ubuntu version of the package is found.
  2653. """
  2654. version = '1.0-1ubuntu1'
  2655. self.set_versions_content([
  2656. (self.package.name, version)
  2657. ])
  2658. self.run_task()
  2659. # Created an ubuntu package
  2660. self.assertEqual(1, UbuntuPackage.objects.count())
  2661. # Has the correct version?
  2662. ubuntu_pkg = UbuntuPackage.objects.all()[0]
  2663. self.assertEqual(version, ubuntu_pkg.version)
  2664. # Linked to the correct package?
  2665. self.assertEqual(self.package.name, ubuntu_pkg.package.name)
  2666. def test_ubuntu_package_removed(self):
  2667. """
  2668. Tests that an existing
  2669. :class:`distro_tracker.vendor.debian.models.UbuntuPackage` instance is
  2670. removed if an Ubuntu version of the package is no longer found.
  2671. """
  2672. version = '1.0-1ubuntu1'
  2673. # Create an old ubuntu package
  2674. UbuntuPackage.objects.create(
  2675. package=self.package,
  2676. version=version)
  2677. self.set_versions_content([])
  2678. self.run_task()
  2679. # The package is removed.
  2680. self.assertEqual(0, UbuntuPackage.objects.count())
  2681. def test_ubuntu_package_bugs_created(self):
  2682. """
  2683. Tests that a :class:`distro_tracker.vendor.debian.models.UbuntuPackage`
  2684. instance which is created for a new Ubuntu package contains the bugs
  2685. count.
  2686. """
  2687. version = '1.0-1ubuntu1'
  2688. self.set_versions_content([
  2689. (self.package.name, version)
  2690. ])
  2691. bug_count, patch_count = 5, 1
  2692. self.set_bugs_content([
  2693. (self.package.name, bug_count, patch_count),
  2694. ])
  2695. self.run_task()
  2696. self.assertEqual(1, UbuntuPackage.objects.count())
  2697. # Has the correct bugs count?
  2698. ubuntu_pkg = UbuntuPackage.objects.all()[0]
  2699. expected = {
  2700. 'bug_count': bug_count,
  2701. 'patch_count': patch_count
  2702. }
  2703. self.assertDictEqual(expected, ubuntu_pkg.bugs)
  2704. def test_ubuntu_package_bugs_updated(self):
  2705. """
  2706. Tests that an existing
  2707. :class:`distro_tracker.vendor.debian.models.UbuntuPackage` instance is
  2708. correctly updated to contain the new Ubuntu package bugs when it
  2709. previously contained no bug information.
  2710. """
  2711. version = '1.0-1ubuntu1'
  2712. UbuntuPackage.objects.create(
  2713. package=self.package,
  2714. version=version)
  2715. bug_count, patch_count = 5, 1
  2716. self.set_bugs_content([
  2717. (self.package.name, bug_count, patch_count),
  2718. ])
  2719. self.set_versions_content([
  2720. (self.package.name, version),
  2721. ])
  2722. self.run_task()
  2723. self.assertEqual(1, UbuntuPackage.objects.count())
  2724. # Has the correct bugs count?
  2725. ubuntu_pkg = UbuntuPackage.objects.all()[0]
  2726. expected = {
  2727. 'bug_count': bug_count,
  2728. 'patch_count': patch_count
  2729. }
  2730. self.assertDictEqual(expected, ubuntu_pkg.bugs)
  2731. def test_ubuntu_package_bugs_updated_existing(self):
  2732. """
  2733. Tests that an existing
  2734. :class:`distro_tracker.vendor.debian.models.UbuntuPackage` instance is
  2735. correctly updated to contain the new Ubuntu package bugs when it
  2736. previously contained older bug information.
  2737. """
  2738. version = '1.0-1ubuntu1'
  2739. UbuntuPackage.objects.create(
  2740. package=self.package,
  2741. version=version,
  2742. bugs={
  2743. 'bug_count': 100,
  2744. 'patch_count': 50,
  2745. })
  2746. bug_count, patch_count = 5, 1
  2747. self.set_bugs_content([
  2748. (self.package.name, bug_count, patch_count),
  2749. ])
  2750. self.set_versions_content([
  2751. (self.package.name, version),
  2752. ])
  2753. self.run_task()
  2754. self.assertEqual(1, UbuntuPackage.objects.count())
  2755. # Has the correct bugs count?
  2756. ubuntu_pkg = UbuntuPackage.objects.all()[0]
  2757. expected = {
  2758. 'bug_count': bug_count,
  2759. 'patch_count': patch_count
  2760. }
  2761. self.assertDictEqual(expected, ubuntu_pkg.bugs)
  2762. def test_ubuntu_package_bug_stats_removed(self):
  2763. """
  2764. Tests that an existing
  2765. :class:`distro_tracker.vendor.debian.models.UbuntuPackage` instance is
  2766. correctly updated to contain no bug stats when there are no bug stats
  2767. found for the package by the update.
  2768. """
  2769. version = '1.0-1ubuntu1'
  2770. UbuntuPackage.objects.create(
  2771. package=self.package,
  2772. version=version,
  2773. bugs={
  2774. 'bug_count': 100,
  2775. 'patch_count': 50,
  2776. })
  2777. self.set_versions_content([
  2778. (self.package.name, version),
  2779. ])
  2780. self.run_task()
  2781. self.assertEqual(1, UbuntuPackage.objects.count())
  2782. # No more bug stats?
  2783. ubuntu_pkg = UbuntuPackage.objects.all()[0]
  2784. self.assertIsNone(ubuntu_pkg.bugs)
  2785. def test_ubuntu_package_diff_created(self):
  2786. """
  2787. Tests that a :class:`distro_tracker.vendor.debian.models.UbuntuPackage`
  2788. instance which is created for a new Ubuntu package contains the diff
  2789. info.
  2790. """
  2791. version = '1.1-1ubuntu1'
  2792. self.set_versions_content([
  2793. (self.package.name, version)
  2794. ])
  2795. # Have the patch version be different than the package version
  2796. patch_version = '1.0-1ubuntu1'
  2797. diff_url = 'd/dummy-package/dummy-package_{ver}.patch'.format(
  2798. ver=patch_version)
  2799. self.set_diff_content([
  2800. (self.package.name, diff_url),
  2801. ])
  2802. self.run_task()
  2803. self.assertEqual(1, UbuntuPackage.objects.count())
  2804. # Has the correct diff info?
  2805. ubuntu_pkg = UbuntuPackage.objects.all()[0]
  2806. expected = {
  2807. 'version': patch_version,
  2808. 'diff_url': diff_url,
  2809. }
  2810. self.assertDictEqual(expected, ubuntu_pkg.patch_diff)
  2811. def test_ubuntu_package_diff_updated(self):
  2812. """
  2813. Tests that an existing
  2814. :class:`distro_tracker.vendor.debian.models.UbuntuPackage` instance is
  2815. correctly updated to contain the new Ubuntu patch diffs when it
  2816. previously contained no diff info.
  2817. """
  2818. # Create an UbuntuPackage with no patch diff info
  2819. version = '1.0-1ubuntu1'
  2820. UbuntuPackage.objects.create(
  2821. package=self.package,
  2822. version=version)
  2823. self.set_versions_content([
  2824. (self.package.name, version)
  2825. ])
  2826. diff_url = 'd/dummy-package/dummy-package_{ver}.patch'.format(
  2827. ver=version)
  2828. self.set_diff_content([
  2829. (self.package.name, diff_url),
  2830. ])
  2831. self.run_task()
  2832. self.assertEqual(1, UbuntuPackage.objects.count())
  2833. # The diff info is updated?
  2834. ubuntu_pkg = UbuntuPackage.objects.all()[0]
  2835. expected = {
  2836. 'version': version,
  2837. 'diff_url': diff_url,
  2838. }
  2839. self.assertDictEqual(expected, ubuntu_pkg.patch_diff)
  2840. def test_ubuntu_package_diff_updated_existing(self):
  2841. """
  2842. Tests that an existing
  2843. :class:`distro_tracker.vendor.debian.models.UbuntuPackage` instance is
  2844. correctly updated to contain the new Ubuntu patch diff info when it
  2845. previously contained older patch diff info.
  2846. """
  2847. version = '1.0-1ubuntu1'
  2848. UbuntuPackage.objects.create(
  2849. package=self.package,
  2850. version=version,
  2851. patch_diff={
  2852. 'version': '1.0-0',
  2853. 'diff_url': 'http://old.url.com',
  2854. })
  2855. self.set_versions_content([
  2856. (self.package.name, version)
  2857. ])
  2858. diff_url = 'd/dummy-package/dummy-package_{ver}.patch'.format(
  2859. ver=version)
  2860. self.set_diff_content([
  2861. (self.package.name, diff_url),
  2862. ])
  2863. self.run_task()
  2864. self.assertEqual(1, UbuntuPackage.objects.count())
  2865. # The diff info is updated?
  2866. ubuntu_pkg = UbuntuPackage.objects.all()[0]
  2867. expected = {
  2868. 'version': version,
  2869. 'diff_url': diff_url,
  2870. }
  2871. self.assertDictEqual(expected, ubuntu_pkg.patch_diff)
  2872. def test_ubuntu_package_diff_removed(self):
  2873. """
  2874. Tests that an existing
  2875. :class:`distro_tracker.vendor.debian.models.UbuntuPackage` instance is
  2876. correctly updated to contain no diff info when there is no diff info
  2877. found for the package by the update.
  2878. """
  2879. version = '1.0-1ubuntu1'
  2880. UbuntuPackage.objects.create(
  2881. package=self.package,
  2882. version=version,
  2883. patch_diff={
  2884. 'version': '1.0-0',
  2885. 'diff_url': 'http://old.url.com',
  2886. })
  2887. self.set_versions_content([
  2888. (self.package.name, version)
  2889. ])
  2890. self.run_task()
  2891. self.assertEqual(1, UbuntuPackage.objects.count())
  2892. # No more patch diff info?
  2893. ubuntu_pkg = UbuntuPackage.objects.all()[0]
  2894. self.assertIsNone(ubuntu_pkg.patch_diff)
  2895. class UbuntuPanelTests(TestCase):
  2896. """
  2897. Tests for the
  2898. :class:`distro_tracker.vendor.debian.tracker_panels.UbuntuPanel` panel.
  2899. """
  2900. def setUp(self):
  2901. self.package = PackageName.objects.create(
  2902. source=True,
  2903. name='dummy-package')
  2904. def get_package_page_response(self, package_name):
  2905. package_page_url = reverse('dtracker-package-page', kwargs={
  2906. 'package_name': package_name,
  2907. })
  2908. return self.client.get(package_page_url)
  2909. def ubuntu_panel_in_content(self, content):
  2910. html = soup(content, 'html.parser')
  2911. for panel in html.findAll('div', {'class': 'panel-heading'}):
  2912. if 'ubuntu' in str(panel):
  2913. return True
  2914. return False
  2915. def test_panel_displayed(self):
  2916. """
  2917. Tests that the panel is displayed when the package has a known Ubuntu
  2918. version.
  2919. """
  2920. # Create the ubuntu version
  2921. ubuntu_version = '1.0.0-ubuntu1'
  2922. UbuntuPackage.objects.create(
  2923. package=self.package,
  2924. version=ubuntu_version)
  2925. response = self.get_package_page_response(self.package.name)
  2926. response_content = response.content.decode('utf8')
  2927. self.assertTrue(self.ubuntu_panel_in_content(response_content))
  2928. self.assertIn(ubuntu_version, response_content)
  2929. def test_panel_not_displayed(self):
  2930. """
  2931. Tests tat the panel is not displayed when the package has no known
  2932. Ubuntu versions.
  2933. """
  2934. # Sanity check: no Ubuntu version for the packag?
  2935. self.assertEqual(
  2936. 0,
  2937. UbuntuPackage.objects.filter(package=self.package).count())
  2938. response = self.get_package_page_response(self.package.name)
  2939. self.assertFalse(self.ubuntu_panel_in_content(response.content))
  2940. def test_bugs_displayed(self):
  2941. """
  2942. Tests that the Ubuntu bug counts are displayed in the Ubuntu panel, if
  2943. they exist for the package.
  2944. """
  2945. ubuntu_version = '1.0.0-ubuntu1'
  2946. bug_count, patch_count = 10, 5
  2947. UbuntuPackage.objects.create(
  2948. package=self.package,
  2949. version=ubuntu_version,
  2950. bugs={
  2951. 'bug_count': bug_count,
  2952. 'patch_count': patch_count,
  2953. })
  2954. response = self.get_package_page_response(self.package.name)
  2955. response_content = response.content.decode('utf8')
  2956. self.assertIn("10 bugs", response_content)
  2957. self.assertIn("5 patches", response_content)
  2958. def test_patch_diff_displayed(self):
  2959. """
  2960. Tests that the Ubuntu patch diff link is displayed in the Ubuntu panel,
  2961. if it exists for the package.
  2962. """
  2963. ubuntu_version = '1.0.0-ubuntu1'
  2964. diff_url = 'd/dummy-package/dummy-package_1.0.0-ubuntu1'
  2965. UbuntuPackage.objects.create(
  2966. package=self.package,
  2967. version=ubuntu_version,
  2968. patch_diff={
  2969. 'diff_url': diff_url,
  2970. 'version': ubuntu_version,
  2971. })
  2972. response = self.get_package_page_response(self.package.name)
  2973. response_content = response.content.decode('utf-8')
  2974. self.assertIn(
  2975. 'patches for {}'.format(ubuntu_version),
  2976. response_content)
  2977. self.assertIn(ubuntu_version, response_content)
  2978. class UpdateWnppStatsTaskTests(TestCase):
  2979. """
  2980. Tests for the
  2981. :class:`distro_tracker.vendor.debian.tracker_tasks.UpdateWnppStatsTask`
  2982. task.
  2983. """
  2984. def setUp(self):
  2985. self.package = SourcePackageName.objects.create(name='dummy-package')
  2986. self.task = UpdateWnppStatsTask()
  2987. # Stub the data providing method
  2988. self.task._get_wnpp_content = mock.MagicMock(return_value='')
  2989. def get_action_item_type(self):
  2990. return ActionItemType.objects.get_or_create(
  2991. type_name=UpdateWnppStatsTask.ACTION_ITEM_TYPE_NAME)[0]
  2992. def set_wnpp_content(self, content):
  2993. """
  2994. Sets the stub wnpp content which the task will retrieve once it runs.
  2995. :param content: A list of (package_name, issues) pairs. ``issues`` is
  2996. a list of dicts describing the WNPP bugs the package has.
  2997. """
  2998. self.task._get_wnpp_content.return_value = '\n'.join(
  2999. '{pkg}: {issues}'.format(
  3000. pkg=pkg,
  3001. issues='|'.join(
  3002. '{type} {bug_id}'.format(
  3003. type=issue['wnpp_type'],
  3004. bug_id=issue['bug_id'])
  3005. for issue in issues))
  3006. for pkg, issues in content)
  3007. def run_task(self):
  3008. self.task.execute()
  3009. def test_action_item_created(self):
  3010. """
  3011. Tests that an :class:`ActionItem
  3012. <distro_tracker.core.models.ActionItem>` instance is created when the
  3013. package has a WNPP bug.
  3014. """
  3015. wnpp_type, bug_id = 'O', 12345
  3016. self.set_wnpp_content([(
  3017. self.package.name, [{
  3018. 'wnpp_type': wnpp_type,
  3019. 'bug_id': bug_id,
  3020. }]
  3021. )])
  3022. self.run_task()
  3023. # An action item has been created
  3024. self.assertEqual(1, ActionItem.objects.count())
  3025. # The item has the correct type and template
  3026. item = ActionItem.objects.all()[0]
  3027. self.assertEqual(
  3028. UpdateWnppStatsTask.ACTION_ITEM_TYPE_NAME,
  3029. item.item_type.type_name)
  3030. self.assertEqual(
  3031. UpdateWnppStatsTask.ACTION_ITEM_TEMPLATE,
  3032. item.full_description_template)
  3033. # The extra data is correctly set?
  3034. expected_data = {
  3035. 'wnpp_type': wnpp_type,
  3036. 'bug_id': bug_id,
  3037. }
  3038. self.assertEqual(expected_data, item.extra_data['wnpp_info'])
  3039. # Test that the short description is correctly set.
  3040. dsc = ('<a href="https://bugs.debian.org/12345">O: This package has'
  3041. ' been orphaned and needs a maintainer.</a>')
  3042. self.assertEqual(dsc, item.short_description)
  3043. def test_action_item_created_unknown_type(self):
  3044. """
  3045. Tests that an :class:`ActionItem
  3046. <distro_tracker.core.models.ActionItem>` instance is created when the
  3047. package has a WNPP bug of an unknown type.
  3048. """
  3049. wnpp_type, bug_id = 'RFC', 12345
  3050. self.set_wnpp_content([(
  3051. self.package.name, [{
  3052. 'wnpp_type': wnpp_type,
  3053. 'bug_id': bug_id,
  3054. }]
  3055. )])
  3056. self.run_task()
  3057. # An action item has been created
  3058. self.assertEqual(1, ActionItem.objects.count())
  3059. # The item has the correct type and template
  3060. item = ActionItem.objects.all()[0]
  3061. self.assertEqual(
  3062. UpdateWnppStatsTask.ACTION_ITEM_TYPE_NAME,
  3063. item.item_type.type_name)
  3064. self.assertEqual(
  3065. UpdateWnppStatsTask.ACTION_ITEM_TEMPLATE,
  3066. item.full_description_template)
  3067. # The extra data is correctly set?
  3068. expected_data = {
  3069. 'wnpp_type': wnpp_type,
  3070. 'bug_id': bug_id,
  3071. }
  3072. self.assertEqual(expected_data, item.extra_data['wnpp_info'])
  3073. # Test that the short description is correctly set.
  3074. dsc = ('<a href="https://bugs.debian.org/12345">RFC: The WNPP database'
  3075. ' contains an entry for this package.</a>')
  3076. self.assertEqual(dsc, item.short_description)
  3077. def test_action_item_updated(self):
  3078. """
  3079. Tests that an existing :class:`ActionItem
  3080. <distro_tracker.core.models.ActionItem>` instance is updated when there
  3081. are changes to the WNPP bug info.
  3082. """
  3083. # Create an existing action item
  3084. old_bug_id = 54321
  3085. old_item = ActionItem.objects.create(
  3086. item_type=self.get_action_item_type(),
  3087. package=self.package,
  3088. extra_data={
  3089. 'wnpp_info': {
  3090. 'wnpp_type': 'O',
  3091. 'bug_id': old_bug_id,
  3092. }
  3093. })
  3094. old_timestamp = old_item.last_updated_timestamp
  3095. # Set new WNPP info
  3096. wnpp_type, bug_id = 'O', 12345
  3097. self.set_wnpp_content([(
  3098. self.package.name, [{
  3099. 'wnpp_type': wnpp_type,
  3100. 'bug_id': bug_id,
  3101. }]
  3102. )])
  3103. self.run_task()
  3104. # Still one action item
  3105. self.assertEqual(1, ActionItem.objects.count())
  3106. # The item has been updated
  3107. item = ActionItem.objects.all()[0]
  3108. self.assertNotEqual(old_timestamp, item.last_updated_timestamp)
  3109. # The extra data is updated as well?
  3110. expected_data = {
  3111. 'wnpp_type': wnpp_type,
  3112. 'bug_id': bug_id,
  3113. }
  3114. self.assertEqual(expected_data, item.extra_data['wnpp_info'])
  3115. def test_action_item_not_updated(self):
  3116. """
  3117. Tests that an existing :class:`ActionItem
  3118. <distro_tracker.core.models.ActionItem>` instance is not updated when
  3119. there are no changes to the WNPP bug info.
  3120. """
  3121. # Create an existing action item
  3122. wnpp_type, bug_id = 'O', 12345
  3123. old_item = ActionItem.objects.create(
  3124. item_type=self.get_action_item_type(),
  3125. package=self.package,
  3126. extra_data={
  3127. 'wnpp_info': {
  3128. 'wnpp_type': wnpp_type,
  3129. 'bug_id': bug_id,
  3130. }
  3131. })
  3132. old_timestamp = old_item.last_updated_timestamp
  3133. # Set "new" WNPP info
  3134. self.set_wnpp_content([(
  3135. self.package.name, [{
  3136. 'wnpp_type': wnpp_type,
  3137. 'bug_id': bug_id,
  3138. }]
  3139. )])
  3140. self.run_task()
  3141. # Still one action item
  3142. self.assertEqual(1, ActionItem.objects.count())
  3143. # The item has not been changed
  3144. item = ActionItem.objects.all()[0]
  3145. self.assertEqual(old_timestamp, item.last_updated_timestamp)
  3146. def test_action_item_removed(self):
  3147. """
  3148. Tests that an existing :class:`ActionItem
  3149. <distro_tracker.core.models.ActionItem>` instance is removed when there
  3150. is no more WNPP bug info.
  3151. """
  3152. # Create an existing action item
  3153. wnpp_type, bug_id = 'O', 12345
  3154. ActionItem.objects.create(
  3155. item_type=self.get_action_item_type(),
  3156. package=self.package,
  3157. extra_data={
  3158. 'wnpp_info': {
  3159. 'wnpp_type': wnpp_type,
  3160. 'bug_id': bug_id,
  3161. },
  3162. })
  3163. # Set "new" WNPP info
  3164. self.run_task()
  3165. # Still one action item
  3166. # No more actino items
  3167. self.assertEqual(0, ActionItem.objects.count())
  3168. def test_action_item_not_created(self):
  3169. """
  3170. Tests that an :class:`ActionItem
  3171. <distro_tracker.core.models.ActionItem>` instance is not created for non
  3172. existing packages.
  3173. """
  3174. wnpp_type, bug_id = 'O', 12345
  3175. self.set_wnpp_content([(
  3176. 'no-exist', [{
  3177. 'wnpp_type': wnpp_type,
  3178. 'bug_id': bug_id,
  3179. }]
  3180. )])
  3181. self.run_task()
  3182. # No action items
  3183. self.assertEqual(0, ActionItem.objects.count())
  3184. def test_action_item_multiple_packages(self):
  3185. """
  3186. Tests that an :class:`ActionItem
  3187. <distro_tracker.core.models.ActionItem>` instance is created for
  3188. multiple packages.
  3189. """
  3190. wnpp = [
  3191. {
  3192. 'wnpp_type': 'O',
  3193. 'bug_id': 12345,
  3194. },
  3195. {
  3196. 'wnpp_type': 'RM',
  3197. 'bug_id': 11111,
  3198. }
  3199. ]
  3200. other_package = PackageName.objects.create(
  3201. name='other-package',
  3202. source=True)
  3203. packages = [other_package, self.package]
  3204. self.set_wnpp_content([
  3205. (package.name, [wnpp_item])
  3206. for package, wnpp_item in zip(packages, wnpp)
  3207. ])
  3208. self.run_task()
  3209. # An action item is created for all packages
  3210. self.assertEqual(2, ActionItem.objects.count())
  3211. for package, wnpp_info in zip(packages, wnpp):
  3212. self.assertEqual(1, package.action_items.count())
  3213. item = package.action_items.all()[0]
  3214. self.assertEqual(wnpp_info, item.extra_data['wnpp_info'])
  3215. @override_settings(
  3216. DISTRO_TRACKER_VENDOR_RULES='distro_tracker.vendor.debian.rules')
  3217. class UpdateNewQueuePackagesTests(TestCase):
  3218. """
  3219. Tests for the
  3220. :class:`distro_tracker.vendor.debian.tracker_tasks.UpdateNewQueuePackages`
  3221. task.
  3222. """
  3223. def setUp(self):
  3224. self.package = SourcePackageName.objects.create(name='dummy-package')
  3225. self.task = UpdateNewQueuePackages()
  3226. # Stub the data providing method
  3227. self.new_content = ''
  3228. self.task._get_new_content = mock.MagicMock(return_value='')
  3229. def add_package_to_new(self, package):
  3230. package_content = '\n'.join(
  3231. '{}: {}'.format(key, value)
  3232. for key, value in package.items())
  3233. self.new_content += package_content + '\n\n'
  3234. def run_task(self):
  3235. self.task._get_new_content.return_value = self.new_content
  3236. self.task.execute()
  3237. def get_new_info(self, package):
  3238. """
  3239. Helper method which returns the package's
  3240. :class:`PackageExtractedInfo
  3241. <distro_tracker.core.models.PackageExtractedInfo>` instance containing
  3242. the NEW queue info, or ``None`` if there is no such instance.
  3243. """
  3244. try:
  3245. return package.packageextractedinfo_set.get(
  3246. key=UpdateNewQueuePackages.EXTRACTED_INFO_KEY)
  3247. except PackageExtractedInfo.DoesNotExist:
  3248. return None
  3249. def test_single_distribution(self):
  3250. """
  3251. Tests that the NEW queue information is correctly extracted when the
  3252. package is found in only one distribution in the NEW queue.
  3253. """
  3254. distribution = 'sid'
  3255. version = '1.0.0'
  3256. self.add_package_to_new({
  3257. 'Version': version,
  3258. 'Source': self.package.name,
  3259. 'Queue': 'new',
  3260. 'Distribution': distribution,
  3261. })
  3262. self.run_task()
  3263. new_info = self.get_new_info(self.package)
  3264. self.assertIsNotNone(new_info)
  3265. # The distribution is found in the info
  3266. self.assertIn(distribution, new_info.value)
  3267. # The correct version is found in the info
  3268. self.assertEqual(version, new_info.value[distribution]['version'])
  3269. def test_single_distribution_multiple_versions(self):
  3270. """
  3271. Tests that the NEW queue information is correctly extracted when the
  3272. package has multiple versions for a distribution.
  3273. """
  3274. distribution = 'sid'
  3275. latest_version = '3.0.0'
  3276. self.add_package_to_new({
  3277. 'Version': '1.0 ' + latest_version + ' 2.0',
  3278. 'Source': self.package.name,
  3279. 'Queue': 'new',
  3280. 'Distribution': distribution,
  3281. })
  3282. self.run_task()
  3283. new_info = self.get_new_info(self.package)
  3284. self.assertIsNotNone(new_info)
  3285. # The distribution is found in the info
  3286. self.assertIn(distribution, new_info.value)
  3287. # The correct version is found in the info
  3288. self.assertEqual(
  3289. latest_version,
  3290. new_info.value[distribution]['version'])
  3291. def test_multiple_distributions(self):
  3292. """
  3293. Tests that the NEW queue information is correctly extracted when the
  3294. package has mutliple distributions in the NEW queue.
  3295. """
  3296. distributions = ['sid', 'stable-security']
  3297. versions = ['1.0.0', '2.0.0']
  3298. for dist, ver in zip(distributions, versions):
  3299. self.add_package_to_new({
  3300. 'Version': ver,
  3301. 'Source': self.package.name,
  3302. 'Queue': 'new',
  3303. 'Distribution': dist,
  3304. })
  3305. self.run_task()
  3306. new_info = self.get_new_info(self.package)
  3307. self.assertIsNotNone(new_info)
  3308. # All distributions found in the info with the correct corresponding
  3309. # version.
  3310. for dist, ver in zip(distributions, versions):
  3311. self.assertIn(dist, new_info.value)
  3312. # The correct version is found in the info
  3313. self.assertEqual(ver, new_info.value[dist]['version'])
  3314. def test_multiple_entries_single_distribution(self):
  3315. """
  3316. Tests that the latest version is always used for a distribution no
  3317. matter if it is found in a separate entry instead of being in the same
  3318. Version field.
  3319. """
  3320. distribution = 'sid'
  3321. latest_version = '3.0.0'
  3322. self.add_package_to_new({
  3323. 'Version': '1.0',
  3324. 'Source': self.package.name,
  3325. 'Queue': 'new',
  3326. 'Distribution': distribution,
  3327. })
  3328. self.add_package_to_new({
  3329. 'Version': latest_version,
  3330. 'Source': self.package.name,
  3331. 'Queue': 'new',
  3332. 'Distribution': distribution,
  3333. })
  3334. self.add_package_to_new({
  3335. 'Version': '2.0',
  3336. 'Source': self.package.name,
  3337. 'Queue': 'new',
  3338. 'Distribution': distribution,
  3339. })
  3340. self.run_task()
  3341. new_info = self.get_new_info(self.package)
  3342. self.assertIsNotNone(new_info)
  3343. # The distribution is found in the info
  3344. self.assertIn(distribution, new_info.value)
  3345. # The correct version is found in the info
  3346. self.assertEqual(
  3347. latest_version,
  3348. new_info.value[distribution]['version'])
  3349. def test_malformed_entry(self):
  3350. """
  3351. Tests that nothing is created when the package's entry is missing the
  3352. Queue field.
  3353. """
  3354. distribution = 'sid'
  3355. version = '1.0.0'
  3356. self.add_package_to_new({
  3357. 'Version': version,
  3358. 'Source': self.package.name,
  3359. 'Distribution': distribution,
  3360. })
  3361. self.run_task()
  3362. new_info = self.get_new_info(self.package)
  3363. self.assertIsNone(new_info)
  3364. def test_entry_updated(self):
  3365. """
  3366. Tests that the NEW info is updated when the entry is updated.
  3367. """
  3368. distribution = 'sid'
  3369. old_version = '1.0.0'
  3370. version = '2.0.0'
  3371. # Create an old entry
  3372. PackageExtractedInfo.objects.create(
  3373. package=self.package,
  3374. key=UpdateNewQueuePackages.EXTRACTED_INFO_KEY,
  3375. value={
  3376. distribution: {
  3377. 'version': old_version,
  3378. }
  3379. })
  3380. self.add_package_to_new({
  3381. 'Version': version,
  3382. 'Source': self.package.name,
  3383. 'Queue': 'new',
  3384. 'Distribution': distribution,
  3385. })
  3386. self.run_task()
  3387. new_info = self.get_new_info(self.package)
  3388. self.assertIsNotNone(new_info)
  3389. # The distribution is found in the info
  3390. self.assertIn(distribution, new_info.value)
  3391. # The correct version is found in the info
  3392. self.assertEqual(version, new_info.value[distribution]['version'])
  3393. def test_entry_dropped(self):
  3394. """
  3395. Tests that the NEW entry is correctly dropped from PackageExtractedInfo
  3396. when the entry is gone.
  3397. """
  3398. distribution = 'sid'
  3399. old_version = '1.0.0'
  3400. # Create an old entry
  3401. PackageExtractedInfo.objects.create(
  3402. package=self.package,
  3403. key=UpdateNewQueuePackages.EXTRACTED_INFO_KEY,
  3404. value={
  3405. distribution: {
  3406. 'version': old_version,
  3407. }
  3408. })
  3409. self.run_task()
  3410. new_info = self.get_new_info(self.package)
  3411. self.assertIsNone(new_info)
  3412. @override_settings(
  3413. DISTRO_TRACKER_VENDOR_RULES='distro_tracker.vendor.debian.rules')
  3414. class NewQueueVersionsPanelTests(TestCase):
  3415. """
  3416. Tests that the NEW queue versions are displayed in the versions panel.
  3417. """
  3418. def setUp(self):
  3419. self.package = PackageName.objects.create(
  3420. source=True,
  3421. name='dummy-package')
  3422. self.package.packageextractedinfo_set.create(
  3423. key='versions', value={})
  3424. def get_package_page_response(self, package_name):
  3425. package_page_url = reverse('dtracker-package-page', kwargs={
  3426. 'package_name': package_name,
  3427. })
  3428. return self.client.get(package_page_url)
  3429. def add_new_queue_entry(self, distribution, version):
  3430. info, _ = PackageExtractedInfo.objects.get_or_create(
  3431. package=self.package, key=UpdateNewQueuePackages.
  3432. EXTRACTED_INFO_KEY)
  3433. if not info.value:
  3434. info.value = {}
  3435. info.value.update({
  3436. distribution: {
  3437. 'version': version,
  3438. }
  3439. })
  3440. info.save()
  3441. def test_single_new_version(self):
  3442. """
  3443. Tests for when a package has a version in NEW for only one
  3444. distribution.
  3445. """
  3446. distribution = 'sid'
  3447. version = '1.0.0~sidtest'
  3448. self.add_new_queue_entry(distribution, version)
  3449. response = self.get_package_page_response(self.package.name)
  3450. response_content = response.content.decode('utf-8')
  3451. self.assertIn('NEW/sid', response_content)
  3452. self.assertIn(version, response_content)
  3453. def test_multiple_distributions(self):
  3454. """
  3455. Tests for when a package has a version in NEW for multiple
  3456. distributions.
  3457. """
  3458. dists = ['sid', 'stable-security']
  3459. versions = ['1.0.0~sidtest', '1.0.0~stable-sec-test']
  3460. for dist, ver in zip(dists, versions):
  3461. self.add_new_queue_entry(dist, ver)
  3462. response = self.get_package_page_response(self.package.name)
  3463. response_content = response.content.decode('utf8')
  3464. for dist, ver in zip(dists, versions):
  3465. self.assertIn('NEW/' + dist, response_content)
  3466. self.assertIn(ver, response_content)
  3467. class ImportOldNewsTests(TestCase):
  3468. """
  3469. Tests the management command for importing old news.
  3470. """
  3471. def create_message(self, subject, from_email, date, content):
  3472. msg = Message()
  3473. msg['Subject'] = subject
  3474. msg['From'] = from_email
  3475. msg['Date'] = date
  3476. msg.set_payload(content.encode('utf-8'), 'utf-8')
  3477. return msg
  3478. def test_news_created(self):
  3479. packages = ['dpkg', 'dummy', 'asdf', '000']
  3480. email = 'user@domain.com'
  3481. subject_template = 'Message to {}'
  3482. content_template = "Hello Öäüßčćž한글{}"
  3483. date = 'Mon, 28 Nov 2005 15:47:11 -0800'
  3484. with make_temp_directory('old-pts') as old_distro_tracker_root:
  3485. # Make the expected directory structure and add some news
  3486. for package in packages:
  3487. PackageName.objects.create(name=package, source=True)
  3488. news_dir = os.path.join(
  3489. old_distro_tracker_root, package[0], package, 'news')
  3490. os.makedirs(news_dir)
  3491. # Add a news for this package
  3492. msg = self.create_message(
  3493. subject_template.format(package),
  3494. email,
  3495. date,
  3496. content_template.format(package))
  3497. with open(os.path.join(news_dir, 'news.txt'), 'wb') as f:
  3498. if hasattr(msg, 'as_bytes'):
  3499. content = msg.as_bytes()
  3500. else:
  3501. content = msg.as_string()
  3502. f.write(content)
  3503. call_command('tracker_import_old_news', old_distro_tracker_root)
  3504. # All news items created
  3505. self.assertEqual(len(packages), News.objects.count())
  3506. # All news item have the correct associated content
  3507. for package in packages:
  3508. news = News.objects.get(package__name=package)
  3509. subject = subject_template.format(package)
  3510. content = content_template.format(package).encode('utf-8')
  3511. self.assertEqual(subject, news.title)
  3512. # The date of the news item is correctly set to the old item's
  3513. # date?
  3514. self.assertEqual(
  3515. '2005 11 28',
  3516. news.datetime_created.strftime('%Y %m %d'))
  3517. # The news item's content can be seamlessly transformed back to
  3518. # an email Message object.
  3519. msg = message_from_bytes(news.content)
  3520. self.assertEqual(subject, msg['Subject'])
  3521. self.assertEqual(content, msg.get_payload(decode=True))
  3522. class ImportOldSubscribersTests(TestCase):
  3523. """
  3524. Tests for the
  3525. :mod:`distro_tracker.vendor.debian.management.commands.tracker_import_old_subscriber_dump`
  3526. management command.
  3527. """
  3528. def setUp(self):
  3529. self.packages = {}
  3530. def set_package_subscribers(self, package, subscribers):
  3531. self.packages[package] = subscribers
  3532. def get_input(self):
  3533. return '\n'.join(
  3534. '{} => [ {} ]'.format(
  3535. package,
  3536. ' '.join(subscribers))
  3537. for package, subscribers in self.packages.items()
  3538. )
  3539. def run_command(self):
  3540. command = ImportOldSubscribersCommand()
  3541. command.stdin = six.StringIO(self.get_input())
  3542. command.handle()
  3543. def assert_subscribed_to_package(self, package, subscribers):
  3544. for subscriber in subscribers:
  3545. self.assertTrue(Subscription.objects.filter(
  3546. package=package,
  3547. email_settings__user_email__email=subscriber).exists())
  3548. def test_non_existing_package_imported(self):
  3549. """
  3550. Test that when a package that is found in the dump being imported
  3551. does not exist, a new "subscription-only" package is automatically
  3552. created.
  3553. """
  3554. package_name = 'new-package'
  3555. subscribers = [
  3556. 'email@domain.com',
  3557. 'other@domain.com',
  3558. ]
  3559. self.set_package_subscribers(package_name, subscribers)
  3560. self.run_command()
  3561. self.assertEqual(1, PackageName.objects.count())
  3562. package = PackageName.objects.all()[0]
  3563. self.assertEqual(package_name, package.name)
  3564. self.assert_subscribed_to_package(package, subscribers)
  3565. def test_existing_package_imported(self):
  3566. """
  3567. Tests that when a package already exists, only a subscription is
  3568. created, without modifying the package.
  3569. """
  3570. package_name = 'new-package'
  3571. SourcePackageName.objects.create(name=package_name)
  3572. subscribers = [
  3573. 'email@domain.com',
  3574. 'other@domain.com',
  3575. ]
  3576. self.set_package_subscribers(package_name, subscribers)
  3577. self.run_command()
  3578. # Still only one package
  3579. self.assertEqual(1, PackageName.objects.count())
  3580. # Still a source package
  3581. self.assertEqual(1, SourcePackageName.objects.count())
  3582. package = PackageName.objects.all()[0]
  3583. self.assertEqual(package_name, package.name)
  3584. # Correct subscribers imported
  3585. self.assert_subscribed_to_package(package, subscribers)
  3586. def test_multiple_subscriptions_imported(self):
  3587. """
  3588. Tests that multiple subscriptions for a single user are imported.
  3589. """
  3590. packages = [
  3591. PackageName.objects.create(name='pkg1', source=True),
  3592. PackageName.objects.create(name='pkg2', source=True),
  3593. ]
  3594. email = 'user@domain.com'
  3595. for package in packages:
  3596. self.set_package_subscribers(package.name, [email])
  3597. self.run_command()
  3598. self.assertEqual(2, PackageName.objects.count())
  3599. # All subscriptions created?
  3600. for package in packages:
  3601. package = PackageName.objects.get(pk=package.pk)
  3602. self.assert_subscribed_to_package(package, [email])
  3603. class ImportTagsTests(TestCase):
  3604. """
  3605. Tests for the management command for importing the dump of user tags
  3606. (subscription-specific keywords and user default keywords).
  3607. """
  3608. def setUp(self):
  3609. self.tags = {}
  3610. def add_subscription_specific_tags(self, email, package, tags):
  3611. self.tags[email + '#' + package] = tags
  3612. def add_default_tags(self, email, tags):
  3613. self.tags[email] = tags
  3614. def get_input(self):
  3615. return '\n'.join(
  3616. '{}: {}'.format(
  3617. email,
  3618. ','.join(tags))
  3619. for email, tags in self.tags.items()
  3620. )
  3621. def run_command(self):
  3622. command = ImportOldTagsCommand()
  3623. command.stdin = six.StringIO(self.get_input())
  3624. command.handle()
  3625. def assert_keyword_sets_equal(self, set1, set2):
  3626. self.assertEqual(len(set1), len(set2))
  3627. for k in set1:
  3628. self.assertIn(k, set2)
  3629. def test_default_keywords_imported(self):
  3630. email = 'user@domain.com'
  3631. keywords = Keyword.objects.all()[:4]
  3632. tags = [k.name for k in keywords]
  3633. self.add_default_tags(email, tags)
  3634. self.run_command()
  3635. self.assertEqual(1, EmailSettings.objects.count())
  3636. settings = EmailSettings.objects.all()[0]
  3637. self.assert_keyword_sets_equal(
  3638. keywords,
  3639. settings.default_keywords.all())
  3640. def test_subscription_specific_keywords_imported(self):
  3641. email = 'user@domain.com'
  3642. package = 'pkg'
  3643. sub = Subscription.objects.create_for(package, email)
  3644. keywords = Keyword.objects.all()[:4]
  3645. tags = [k.name for k in keywords]
  3646. self.add_subscription_specific_tags(email, package, tags)
  3647. self.run_command()
  3648. # The subscription is updated to contain a new set of keywords
  3649. sub = Subscription.objects.get(pk=sub.pk)
  3650. self.assert_keyword_sets_equal(
  3651. keywords,
  3652. sub.keywords.all())
  3653. def test_both_types_imported(self):
  3654. email = 'user@domain.com'
  3655. package = 'pkg'
  3656. sub = Subscription.objects.create_for(package, email)
  3657. keywords = Keyword.objects.all()[:4]
  3658. tags = [k.name for k in keywords]
  3659. self.add_subscription_specific_tags(email, package, tags)
  3660. default_keywords = Keyword.objects.all()[4:]
  3661. self.add_default_tags(email, [k.name for k in default_keywords])
  3662. self.run_command()
  3663. # The subscription is updated to contain a new set of keywords
  3664. sub = Subscription.objects.get(pk=sub.pk)
  3665. self.assert_keyword_sets_equal(
  3666. keywords,
  3667. sub.keywords.all())
  3668. # The user's default keywords are also updated
  3669. settings = EmailSettings.objects.all()[0]
  3670. self.assert_keyword_sets_equal(
  3671. default_keywords,
  3672. settings.default_keywords.all())
  3673. def test_legacy_mapping_import(self):
  3674. keyword = Keyword.objects.get(name='archive')
  3675. old_tag = 'katie-other'
  3676. email = 'user@domain.com'
  3677. self.add_default_tags(email, [old_tag])
  3678. self.run_command()
  3679. settings = EmailSettings.objects.all()[0]
  3680. self.assert_keyword_sets_equal(
  3681. [keyword],
  3682. settings.default_keywords.all())
  3683. @mock.patch(
  3684. 'distro_tracker.vendor.debian.sso_auth.'
  3685. 'DebianSsoUserBackend.get_user_details')
  3686. @override_settings(MIDDLEWARE_CLASSES=(
  3687. 'django.middleware.common.CommonMiddleware',
  3688. 'django.contrib.sessions.middleware.SessionMiddleware',
  3689. 'django.middleware.csrf.CsrfViewMiddleware',
  3690. 'django.contrib.auth.middleware.AuthenticationMiddleware',
  3691. 'distro_tracker.vendor.debian.sso_auth.DebianSsoUserMiddleware',
  3692. ), AUTHENTICATION_BACKENDS=(
  3693. 'distro_tracker.vendor.debian.sso_auth.DebianSsoUserBackend',
  3694. 'django_email_accounts.auth.UserEmailBackend',
  3695. ))
  3696. class DebianSsoLoginTests(TestCase):
  3697. """
  3698. Tests relating to logging in via the sso.debian.org
  3699. via DACS (which sets REMOTE_USER).
  3700. """
  3701. DD_USER = 'DEBIANORG::DEBIAN:user'
  3702. DD_EMAIL = 'user@debian.org'
  3703. ALIOTH_USER = 'DEBIANORG::DEBIAN:foo-guest@users.alioth.debian.org'
  3704. ALIOTH_EMAIL = 'foo-guest@users.alioth.debian.org'
  3705. INVALID_USER = 'FEDERATION::JURISDICTION:user'
  3706. def get_page(self, remote_user=None):
  3707. self.client.get(reverse('dtracker-index'), **{
  3708. 'REMOTE_USER': remote_user,
  3709. })
  3710. def assert_user_logged_in(self, user):
  3711. self.assertEqual(int(self.client.session['_auth_user_id']), user.pk)
  3712. def assert_no_user_logged_in(self):
  3713. self.assertNotIn('_auth_user_id', self.client.session)
  3714. def test_first_log_in(self, get_user_details):
  3715. """
  3716. Tests that when a Debian Developer first logs in an account is
  3717. automatically created.
  3718. """
  3719. first_name, last_name = 'First', 'Last'
  3720. get_user_details.return_value = {
  3721. 'first_name': first_name,
  3722. 'last_name': last_name,
  3723. }
  3724. self.get_page(self.DD_USER)
  3725. self.assertEqual(1, User.objects.count())
  3726. user = User.objects.all()[0]
  3727. self.assertEqual(first_name, user.first_name)
  3728. self.assertEqual(last_name, user.last_name)
  3729. self.assertEqual(self.DD_EMAIL, user.main_email)
  3730. self.assert_user_logged_in(user)
  3731. def test_first_log_in_via_alioth(self, get_user_details):
  3732. """
  3733. Tests that when an Alioth user first logs in an account is
  3734. automatically created.
  3735. """
  3736. get_user_details.return_value = None
  3737. self.get_page(self.ALIOTH_USER)
  3738. self.assertEqual(1, User.objects.count())
  3739. user = User.objects.all()[0]
  3740. self.assertEqual(self.ALIOTH_EMAIL, user.main_email)
  3741. self.assert_user_logged_in(user)
  3742. def test_no_log_in_invalid_username(self, get_user_details):
  3743. """
  3744. Tests that no user is logged in when the federation or jurisdiction are
  3745. incorrect.
  3746. """
  3747. self.get_page(self.INVALID_USER)
  3748. self.assertEqual(0, User.objects.count())
  3749. self.assert_no_user_logged_in()
  3750. def test_first_log_in_preexisting(self, get_user_details):
  3751. """
  3752. Tests that an already existing user is logged in without modifying the
  3753. account fields.
  3754. """
  3755. old_name = 'Oldname'
  3756. user = User.objects.create_user(
  3757. main_email=self.DD_EMAIL,
  3758. first_name=old_name)
  3759. self.get_page(self.DD_USER)
  3760. self.assertEqual(1, User.objects.count())
  3761. user = User.objects.all()[0]
  3762. self.assertEqual(old_name, user.first_name)
  3763. self.assert_user_logged_in(user)
  3764. def test_first_log_in_preexisting_associated(self, get_user_details):
  3765. """
  3766. Tests that an already existing user that has an associated
  3767. (not main_email) @debian.org address is logged in without modifying the
  3768. account fields.
  3769. """
  3770. old_name = 'Oldname'
  3771. user = User.objects.create_user(
  3772. main_email='user@domain.com',
  3773. first_name=old_name)
  3774. # The @debian.org address is an associated email
  3775. user.emails.create(email=self.DD_EMAIL)
  3776. self.get_page(self.DD_USER)
  3777. self.assertEqual(1, User.objects.count())
  3778. user = User.objects.all()[0]
  3779. self.assertEqual(old_name, user.first_name)
  3780. self.assert_user_logged_in(user)
  3781. def test_first_log_in_preexisting_useremail(self, get_user_details):
  3782. UserEmail.objects.create(email=self.DD_EMAIL)
  3783. self.get_page(self.DD_USER)
  3784. self.assertEqual(1, User.objects.count())
  3785. self.assertTrue(get_user_details.called)
  3786. def test_user_logged_out(self, get_user_details):
  3787. """
  3788. Tests that Distro Tracker logs out the user after the SSO headers are
  3789. invalid.
  3790. """
  3791. user = User.objects.create_user(
  3792. main_email=self.DD_EMAIL)
  3793. self.client.login(remote_user=user.main_email)
  3794. # Sanity check: the user is logged in
  3795. self.assert_user_logged_in(user)
  3796. self.get_page()
  3797. self.assert_no_user_logged_in()
  3798. def test_user_logged_out_no_header(self, get_user_details):
  3799. """
  3800. Tests that Distro Tracker logs out the user if the SSO headers are gone.
  3801. """
  3802. user = User.objects.create_user(
  3803. main_email=self.DD_EMAIL)
  3804. self.client.login(remote_user=user.main_email)
  3805. # Sanity check: the user is logged in
  3806. self.assert_user_logged_in(user)
  3807. self.client.get('/')
  3808. self.assert_no_user_logged_in()
  3809. def test_authenticate_returns_correct_class(self, get_user_details):
  3810. auth_backend = DebianSsoUserBackend()
  3811. user = auth_backend.authenticate(self.DD_EMAIL)
  3812. self.assertIsInstance(user, User) # from distro_tracker.accounts.models
  3813. def test_authenticate_returns_correct_class_with_existing_user(
  3814. self, get_user_details):
  3815. User.objects.create_user(main_email=self.DD_EMAIL)
  3816. auth_backend = DebianSsoUserBackend()
  3817. user = auth_backend.authenticate(self.DD_EMAIL)
  3818. self.assertIsInstance(user, User) # from distro_tracker.accounts.models
  3819. class DebianSsoLoginWithSSLClientCertificateTests(DebianSsoLoginTests):
  3820. """
  3821. Tests relating to logging in with a SSL client certificate
  3822. generated on sso.debian.org.
  3823. See https://wiki.debian.org/DebianSingleSignOn
  3824. """
  3825. DD_USER = 'user@debian.org'
  3826. DD_EMAIL = 'user@debian.org'
  3827. ALIOTH_USER = 'foo-guest@users.alioth.debian.org'
  3828. ALIOTH_EMAIL = 'foo-guest@users.alioth.debian.org'
  3829. def get_page(self, remote_user=None):
  3830. self.client.get(reverse('dtracker-index'), **{
  3831. 'SSL_CLIENT_S_DN_CN': remote_user,
  3832. })
  3833. def test_no_log_in_invalid_username(self):
  3834. # This test does not make sense here, there are no invalid
  3835. # values
  3836. pass
  3837. @mock.patch('distro_tracker.core.utils.http.requests')
  3838. class UpdateDebianDuckTaskTest(TestCase):
  3839. """
  3840. Tests for the
  3841. :class:`distro_tracker.vendor.debian.tracker_tasks.UpdateDebianDuckTask`
  3842. task.
  3843. """
  3844. def setUp(self):
  3845. self.dummy_package = SourcePackageName.objects.create(
  3846. name='dummy-package')
  3847. self.other_package = SourcePackageName.objects.create(
  3848. name='other-package')
  3849. self.duck_data = """
  3850. dummy-package
  3851. dummy-package2
  3852. """
  3853. def run_task(self):
  3854. """
  3855. Runs the Duck status update task.
  3856. """
  3857. task = UpdateDebianDuckTask()
  3858. task.execute()
  3859. def test_action_item_when_in_list(self, mock_requests):
  3860. """
  3861. Tests that an ActionItem is created for a package reported by duck.
  3862. """
  3863. set_mock_response(mock_requests, text=self.duck_data)
  3864. self.run_task()
  3865. self.assertEqual(1, self.dummy_package.action_items.count())
  3866. def test_no_action_item_when_not_in_list(self, mock_requests):
  3867. """
  3868. Tests that no ActionItem is created for a package not reported by duck.
  3869. """
  3870. set_mock_response(mock_requests, text=self.duck_data)
  3871. self.run_task()
  3872. self.assertEqual(0, self.other_package.action_items.count())
  3873. def test_action_item_is_dropped_when_duck_reports_nothing_again(
  3874. self,
  3875. mock_requests):
  3876. """
  3877. Tests that ActionItems are dropped when a package was previousy reported
  3878. but is now not reported anymore.
  3879. """
  3880. set_mock_response(mock_requests, text=self.duck_data)
  3881. self.run_task()
  3882. self.assertEqual(1, self.dummy_package.action_items.count())
  3883. duck_data = """
  3884. yet-another-package
  3885. """
  3886. set_mock_response(mock_requests, text=duck_data)
  3887. self.run_task()
  3888. self.assertEqual(0, self.dummy_package.action_items.count())
  3889. @mock.patch('distro_tracker.core.utils.http.requests')
  3890. class UpdateDebciStatusTaskTest(TestCase):
  3891. """
  3892. Tests for the
  3893. :class:`distro_tracker.vendor.debian.tracker_tasks.UpdateDebciStatusTask`
  3894. task.
  3895. """
  3896. def setUp(self):
  3897. self.dummy_package = SourcePackageName.objects.create(
  3898. name='dummy-package')
  3899. self.other_package = SourcePackageName.objects.create(
  3900. name='other-package')
  3901. self.json_data = """[
  3902. {
  3903. "run_id": "20140705_145427",
  3904. "package": "dummy-package",
  3905. "version": "1.0-1",
  3906. "date": "2014-07-05 14:55:57",
  3907. "status": "pass",
  3908. "blame": [ ],
  3909. "previous_status": "pass",
  3910. "duration_seconds": "91",
  3911. "duration_human": "0h 1m 31s",
  3912. "message": "All tests passed"
  3913. },
  3914. {
  3915. "run_id": "20140705_212616",
  3916. "package": "other-package",
  3917. "version": "2.0-2",
  3918. "date": "2014-07-05 21:34:22",
  3919. "status": "fail",
  3920. "blame": [ ],
  3921. "previous_status": "fail",
  3922. "duration_seconds": "488",
  3923. "duration_human": "0h 8m 8s",
  3924. "message": "Tests failed"
  3925. },
  3926. {
  3927. "run_id": "20140705_143518",
  3928. "package": "another-package",
  3929. "version": "3.0-3",
  3930. "date": "2014-07-05 17:33:08",
  3931. "status": "fail",
  3932. "blame": [ ],
  3933. "previous_status": "fail",
  3934. "duration_seconds": "222",
  3935. "duration_human": "0h 3m 42s",
  3936. "message": "Tests failed"
  3937. }]
  3938. """
  3939. def run_task(self):
  3940. """
  3941. Runs the debci status update task.
  3942. """
  3943. task = UpdateDebciStatusTask()
  3944. task.execute()
  3945. def test_no_action_item_for_passing_test(self, mock_requests):
  3946. """
  3947. Tests that an ActionItem isn't created for a passing debci status.
  3948. """
  3949. set_mock_response(mock_requests, text=self.json_data)
  3950. self.run_task()
  3951. self.assertEqual(0, self.dummy_package.action_items.count())
  3952. def test_no_action_item_for_unknown_package(self, mock_requests):
  3953. """
  3954. Tests that an ActionItem isn't created for an unknown package.
  3955. """
  3956. json_data = """
  3957. [{
  3958. "run_id": "20140705_143518",
  3959. "package": "another-package",
  3960. "version": "3.0-3",
  3961. "date": "2014-07-05 17:33:08",
  3962. "status": "fail",
  3963. "blame": [ ],
  3964. "previous_status": "fail",
  3965. "duration_seconds": "222",
  3966. "duration_human": "0h 3m 42s",
  3967. "message": "Tests failed"
  3968. }]
  3969. """
  3970. set_mock_response(mock_requests, text=json_data)
  3971. self.run_task()
  3972. self.assertEqual(0, ActionItem.objects.count())
  3973. def test_action_item_for_failing_test(self, mock_requests):
  3974. """
  3975. Tests that a proper ActionItem is created for a failing test
  3976. on a known package.
  3977. """
  3978. set_mock_response(mock_requests, text=self.json_data)
  3979. self.run_task()
  3980. # Check that the ActionItem contains the correct contents.
  3981. self.assertEqual(self.other_package.action_items.count(), 1)
  3982. action_item = self.other_package.action_items.all()[0]
  3983. url = "https://ci.debian.net/packages/o/other-package/"
  3984. log = "https://ci.debian.net/data/packages/unstable/amd64/o/" + \
  3985. "other-package/latest-autopkgtest/log.gz"
  3986. self.assertIn(url, action_item.short_description)
  3987. self.assertIn(log, action_item.short_description)
  3988. self.assertEqual(action_item.extra_data['duration'], "0h 8m 8s")
  3989. self.assertEqual(action_item.extra_data['previous_status'], "fail")
  3990. self.assertEqual(action_item.extra_data['date'], "2014-07-05 21:34:22")
  3991. self.assertEqual(action_item.extra_data['url'], url)
  3992. self.assertEqual(action_item.extra_data['log'], log)
  3993. def test_action_item_is_dropped_when_test_passes_again(
  3994. self,
  3995. mock_requests):
  3996. """
  3997. Tests that ActionItems are dropped when the test passes again.
  3998. """
  3999. set_mock_response(mock_requests, text=self.json_data)
  4000. self.run_task()
  4001. json_data = """
  4002. [{
  4003. "run_id": "20140705_143519",
  4004. "package": "other-package",
  4005. "version": "3.0-4",
  4006. "date": "2014-07-07 17:33:08",
  4007. "status": "pass",
  4008. "blame": [ ],
  4009. "previous_status": "fail",
  4010. "duration_seconds": "222",
  4011. "duration_human": "0h 3m 42s",
  4012. "message": "Tests passed"
  4013. }]
  4014. """
  4015. set_mock_response(mock_requests, text=json_data)
  4016. self.run_task()
  4017. self.assertEqual(self.other_package.action_items.count(), 0)
  4018. def test_action_item_is_dropped_when_info_vanishes(self, mock_requests):
  4019. """
  4020. Tests that ActionItems are dropped when the debci report doesn't
  4021. mention the package.
  4022. """
  4023. set_mock_response(mock_requests, text=self.json_data)
  4024. self.run_task()
  4025. set_mock_response(mock_requests, text="[]")
  4026. self.run_task()
  4027. self.assertEqual(ActionItem.objects.count(), 0)
  4028. def test_lib_package_link(self, mock_requests):
  4029. """
  4030. Tests that links to lib packages' log files are correct.
  4031. """
  4032. libpackage = SourcePackageName.objects.create(name='libpackage')
  4033. json_data = """
  4034. [{
  4035. "run_id": "20140705_143518",
  4036. "package": "libpackage",
  4037. "version": "3.0-3",
  4038. "date": "2014-07-05 17:33:08",
  4039. "status": "fail",
  4040. "blame": [ ],
  4041. "previous_status": "fail",
  4042. "duration_seconds": "222",
  4043. "duration_human": "0h 3m 42s",
  4044. "message": "Tests failed"
  4045. }]
  4046. """
  4047. set_mock_response(mock_requests, text=json_data)
  4048. self.run_task()
  4049. action_item = libpackage.action_items.all()[0]
  4050. action_item_log_url = action_item.extra_data['log']
  4051. log_url = "https://ci.debian.net/data/packages/unstable/amd64/libp/" + \
  4052. "libpackage/latest-autopkgtest/log.gz"
  4053. self.assertEqual(action_item_log_url, log_url)
  4054. @mock.patch('distro_tracker.core.utils.http.requests')
  4055. class UpdateAutoRemovalsStatsTaskTest(TestCase):
  4056. """
  4057. Tests for the :class:`distro_tracker.vendor.debian.tracker_tasks
  4058. .UpdateAutoRemovalsStatsTask` task.
  4059. """
  4060. def setUp(self):
  4061. self.dummy_package = SourcePackageName.objects.create(
  4062. name='dummy-package')
  4063. self.other_package = SourcePackageName.objects.create(
  4064. name='other-package')
  4065. self.autoremovals_data = """
  4066. dummy-package:
  4067. bugs:
  4068. - '12345'
  4069. removal_date: 2014-08-24 10:20:00
  4070. dummy-package2:
  4071. bugs:
  4072. - '123456'
  4073. removal_date: 2014-08-25 12:00:00
  4074. """
  4075. def run_task(self):
  4076. """
  4077. Runs the autoremovals status update task.
  4078. """
  4079. task = UpdateAutoRemovalsStatsTask()
  4080. task.execute()
  4081. def test_action_item_when_in_list(self, mock_requests):
  4082. """
  4083. Tests that an ActionItem is created for a package reported by
  4084. autoremovals.
  4085. """
  4086. set_mock_response(mock_requests, text=self.autoremovals_data)
  4087. self.run_task()
  4088. self.assertEqual(1, self.dummy_package.action_items.count())
  4089. def test_no_action_item_when_not_in_list(self, mock_requests):
  4090. """
  4091. Tests that no ActionItem is created for a package not reported by
  4092. autoremovals.
  4093. """
  4094. set_mock_response(mock_requests, text=self.autoremovals_data)
  4095. self.run_task()
  4096. self.assertEqual(0, self.other_package.action_items.count())
  4097. def test_action_item_is_dropped_when_autoremovals_reports_nothing_again(
  4098. self, mock_requests):
  4099. """
  4100. Tests that ActionItems are dropped when a package was previousy
  4101. reported but is now not reported anymore.
  4102. """
  4103. set_mock_response(mock_requests, text=self.autoremovals_data)
  4104. self.run_task()
  4105. self.assertEqual(1, self.dummy_package.action_items.count())
  4106. autoremovals_data = """
  4107. dummy-package3:
  4108. bugs:
  4109. - '1234567'
  4110. removal_date: 2014-08-22 12:21:00
  4111. """
  4112. set_mock_response(mock_requests, text=autoremovals_data)
  4113. self.run_task()
  4114. self.assertEqual(0, self.dummy_package.action_items.count())
  4115. @mock.patch('distro_tracker.core.utils.http.requests')
  4116. class UpdatePackageScreenshotsTaskTest(TestCase):
  4117. """
  4118. Tests for the:class:`distro_tracker.vendor.debian.tracker_tasks.
  4119. UpdatePackageScreenshotsTask` task.
  4120. """
  4121. def setUp(self):
  4122. self.dummy_package = SourcePackageName.objects.create(name='dummy')
  4123. self.json_data = """{
  4124. "packages": [{
  4125. "maintainer": "Jane Doe",
  4126. "name": "dummy",
  4127. "url": "https://screenshots.debian.net/package/dummy",
  4128. "section": "universe/games",
  4129. "maintainer_email": "jane@example.com",
  4130. "homepage": "http://example.com/packages/dummy",
  4131. "description": "a game that you can play"
  4132. }]}
  4133. """
  4134. PackageExtractedInfo.objects.create(
  4135. package=self.dummy_package,
  4136. key='general',
  4137. value={
  4138. 'name': 'dummy',
  4139. 'maintainer': {
  4140. 'email': 'jane@example.com',
  4141. }
  4142. }
  4143. )
  4144. self.other_json_data = """{
  4145. "packages": [{
  4146. "maintainer": "John Doe",
  4147. "name": "other",
  4148. "url": "https://screenshots.debian.net/package/other",
  4149. "section": "universe/games",
  4150. "maintainer_email": "john@example.com",
  4151. "homepage": "http://example.com/packages/other",
  4152. "description": "yet another game that you can play"
  4153. }]}
  4154. """
  4155. def run_task(self):
  4156. """
  4157. Runs the screenshots status update task.
  4158. """
  4159. task = UpdatePackageScreenshotsTask()
  4160. task.execute()
  4161. def test_extractedinfo_item_for_without_screenshot(self, mock_requests):
  4162. """
  4163. Tests that packages without screenshots don't claim to have them.
  4164. """
  4165. set_mock_response(mock_requests, text=self.json_data)
  4166. other_package = SourcePackageName.objects.create(name='other-package')
  4167. self.run_task()
  4168. with self.assertRaises(PackageExtractedInfo.DoesNotExist):
  4169. other_package.packageextractedinfo_set.get(key='screenshots')
  4170. def test_no_extractedinfo_for_unknown_package(self, mock_requests):
  4171. """
  4172. Tests that UpdatePackageScreenshotsTask doesn't fail with an unknown
  4173. package.
  4174. """
  4175. data = """{
  4176. "packages": [{
  4177. "maintainer": "John Doe",
  4178. "name": "other",
  4179. "url": "https://screenshots.debian.net/package/other",
  4180. "section": "universe/games",
  4181. "maintainer_email": "john@example.com",
  4182. "homepage": "http://example.com/packages/other",
  4183. "description": "yet another game that you can play"
  4184. }]}
  4185. """
  4186. set_mock_response(mock_requests, text=data)
  4187. self.run_task()
  4188. count = PackageExtractedInfo.objects.filter(key='screenshots').count()
  4189. self.assertEqual(0, count)
  4190. def test_extractedinfo_for_package_with_screenshots(self, mock_requests):
  4191. """
  4192. Tests that PackageExtractedInfo for a package with a screenshot is
  4193. correct.
  4194. """
  4195. set_mock_response(mock_requests, text=self.json_data)
  4196. self.run_task()
  4197. info = \
  4198. self.dummy_package.packageextractedinfo_set.get(key='screenshots')
  4199. self.assertEqual(info.value['screenshots'], 'true')
  4200. def test_extractedinfo_is_dropped_when_no_more_screenshot(self,
  4201. mock_requests):
  4202. """
  4203. Tests that PackageExtractedInfo is dropped if screenshot goes away.
  4204. """
  4205. set_mock_response(mock_requests, text=self.json_data)
  4206. self.run_task()
  4207. set_mock_response(mock_requests, text=self.other_json_data)
  4208. self.run_task()
  4209. with self.assertRaises(PackageExtractedInfo.DoesNotExist):
  4210. self.dummy_package.packageextractedinfo_set.get(key='screenshots')
  4211. def test_other_extractedinfo_keys_not_dropped(self, mock_requests):
  4212. """
  4213. Ensure that other PackageExtractedInfo keys are not dropped when
  4214. deleting the screenshot key.
  4215. """
  4216. set_mock_response(mock_requests, text=self.json_data)
  4217. self.run_task()
  4218. set_mock_response(mock_requests, text=self.other_json_data)
  4219. self.run_task()
  4220. info = self.dummy_package.packageextractedinfo_set.get(key='general')
  4221. self.assertEqual(info.value['name'], 'dummy')
  4222. @mock.patch('distro_tracker.core.utils.http.requests')
  4223. class UpdateBuildReproducibilityTaskTest(TestCase):
  4224. """
  4225. Tests for the:class:`distro_tracker.vendor.debian.tracker_tasks.
  4226. UpdateBuildReproducibilityTask` task.
  4227. """
  4228. def setUp(self):
  4229. self.json_data = """
  4230. [{
  4231. "package": "dummy",
  4232. "version": "1.2-3",
  4233. "status": "unreproducible",
  4234. "suite": "sid"
  4235. }]
  4236. """
  4237. self.other_json_data = """
  4238. [{
  4239. "package": "other",
  4240. "version": "1.2-3",
  4241. "status": "unreproducible",
  4242. "suite": "sid"
  4243. }]
  4244. """
  4245. self.dummy_package = SourcePackageName.objects.create(name='dummy')
  4246. PackageExtractedInfo.objects.create(
  4247. package=self.dummy_package,
  4248. key='general',
  4249. value={
  4250. 'name': 'dummy',
  4251. 'maintainer': {
  4252. 'email': 'jane@example.com',
  4253. }
  4254. }
  4255. )
  4256. def run_task(self):
  4257. """
  4258. Runs the build reproducibility status update task.
  4259. """
  4260. task = UpdateBuildReproducibilityTask()
  4261. task.execute()
  4262. def test_extractedinfo_without_reproducibility(self, mock_requests):
  4263. """
  4264. Tests that packages without reproducibility info don't claim to have
  4265. them.
  4266. """
  4267. set_mock_response(mock_requests, text=self.json_data)
  4268. other_package = SourcePackageName.objects.create(name='other-package')
  4269. self.run_task()
  4270. with self.assertRaises(PackageExtractedInfo.DoesNotExist):
  4271. other_package.packageextractedinfo_set.get(key='reproducibility')
  4272. def test_no_extractedinfo_for_unknown_package(self, mock_requests):
  4273. """
  4274. Tests that BuildReproducibilityTask doesn't fail with an unknown
  4275. package.
  4276. """
  4277. set_mock_response(mock_requests, text=self.other_json_data)
  4278. self.run_task()
  4279. count = PackageExtractedInfo.objects.filter(
  4280. key='reproducibility').count()
  4281. self.assertEqual(0, count)
  4282. def test_extractedinfo_with_reproducibility(self, mock_requests):
  4283. """
  4284. Tests that PackageExtractedInfo for a package with reproducibility info
  4285. is correct.
  4286. """
  4287. set_mock_response(mock_requests, text=self.json_data)
  4288. self.run_task()
  4289. info = self.dummy_package.packageextractedinfo_set.get(
  4290. key='reproducibility')
  4291. self.assertEqual(info.value['reproducibility'], 'unreproducible')
  4292. action_items = self.dummy_package.action_items
  4293. self.assertEqual(action_items.count(), 1)
  4294. self.assertEqual(action_items.first().item_type.type_name,
  4295. UpdateBuildReproducibilityTask.ACTION_ITEM_TYPE_NAME)
  4296. def test_extractedinfo_is_dropped_when_data_is_gone(self, mock_requests):
  4297. """
  4298. Tests that PackageExtractedInfo is dropped if reproducibility info
  4299. goes away.
  4300. """
  4301. set_mock_response(mock_requests, text=self.json_data)
  4302. self.run_task()
  4303. set_mock_response(mock_requests, text=self.other_json_data)
  4304. self.run_task()
  4305. with self.assertRaises(PackageExtractedInfo.DoesNotExist):
  4306. self.dummy_package.packageextractedinfo_set.get(
  4307. key='reproducibility')
  4308. self.assertEqual(self.dummy_package.action_items.count(), 0)
  4309. def test_action_item_is_dropped_when_status_is_reproducible(self,
  4310. mock_requests):
  4311. """
  4312. Ensure the action item is dropped when status switches from
  4313. unreproducible to reproducible.
  4314. """
  4315. set_mock_response(mock_requests, text=self.json_data)
  4316. self.run_task()
  4317. self.assertEqual(self.dummy_package.action_items.count(), 1)
  4318. json_data = """
  4319. [{
  4320. "package": "dummy",
  4321. "version": "1.2-3",
  4322. "status": "reproducible",
  4323. "suite": "sid"
  4324. }]
  4325. """
  4326. set_mock_response(mock_requests, text=json_data)
  4327. self.run_task()
  4328. self.assertEqual(self.dummy_package.action_items.count(), 0)
  4329. def test_other_extractedinfo_keys_not_dropped(self, mock_requests):
  4330. """
  4331. Ensure that other PackageExtractedInfo keys are not dropped when
  4332. deleting the reproducibility key.
  4333. """
  4334. set_mock_response(mock_requests, text=self.json_data)
  4335. self.run_task()
  4336. set_mock_response(mock_requests, text=self.other_json_data)
  4337. self.run_task()
  4338. info = self.dummy_package.packageextractedinfo_set.get(key='general')
  4339. self.assertEqual(info.value['name'], 'dummy')