This commit is contained in:
Mathieu Agopian 2014-11-19 17:36:40 +01:00
Родитель c70fd33560
Коммит 7c9c856873
75 изменённых файлов: 1702 добавлений и 476 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -46,3 +46,6 @@ media/marketplace-stats
*.po~
site-static/*
user-media/*
.tox/
.cache/
MANIFEST

Просмотреть файл

@ -1,7 +1,12 @@
language: python
python:
- "2.6"
- "2.7"
env:
- TOX_ENV=flake8
- TOX_ENV=docs
- TOX_ENV=es
- TOX_ENV=addons-devhub-editors
- TOX_ENV=main
services:
- memcached
before_install:
@ -9,14 +14,11 @@ before_install:
- wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.3.2.deb && sudo dpkg -i elasticsearch-1.3.2.deb
- sudo /usr/share/elasticsearch/bin/elasticsearch -d -D es.path.data=/tmp -D es.gateway.type=none -D es.index.store.type=memory -D es.discovery.zen.ping.multicast.enabled=false
install:
- make update_deps
- npm install
- pip install tox
before_script:
- mysql -e 'create database olympia;'
- make update_assets
script:
- DJANGO_SETTINGS_MODULE=settings_ci make flake8 && DJANGO_SETTINGS_MODULE=settings_ci make test
- make docs SPHINXOPTS='-nW'
- tox -v -e $TOX_ENV --recreate
notifications:
irc:
channels:

Просмотреть файл

@ -1,4 +1,4 @@
.PHONY: help docs test test_force_db tdd test_failed initialize_db populate_data update_code update_deps update_db update_assets full_init full_update reindex flake8
.PHONY: help docs test test_es test_no_es test_force_db tdd test_failed initialize_db populate_data update_code update_deps update_db update_assets full_init full_update reindex flake8
NUM_ADDONS=10
NUM_THEMES=$(NUM_ADDONS)
@ -26,16 +26,22 @@ docs:
$(MAKE) -C docs html
test:
REUSE_DB=1 python manage.py test --with-blockage --noinput --logging-clear-handlers --with-id -v 2 $(ARGS)
py.test $(ARGS)
test_es:
py.test -m es_tests $(ARGS)
test_no_es:
py.test -m "not es_tests" $(ARGS)
test_force_db:
python manage.py test --with-blockage --noinput --logging-clear-handlers --with-id -v 2 $(ARGS)
py.test --create-db $(ARGS)
tdd:
REUSE_DB=1 python manage.py test --with-blockage --noinput --failfast --pdb --with-id -v 2 $(ARGS)
py.test -x --pdb $(ARGS)
test_failed:
REUSE_DB=1 python manage.py test --with-blockage --noinput --logging-clear-handlers --with-id -v 2 --failed $(ARGS)
py.test --lf $(ARGS)
initialize_db:
python manage.py reset_db

Просмотреть файл

@ -1,6 +1,7 @@
from django.http import HttpRequest
import mock
import pytest
from nose.tools import assert_false
import amo
@ -14,6 +15,9 @@ from .acl import (action_allowed, check_addon_ownership, check_ownership,
match_rules)
pytestmark = pytest.mark.django_db
def test_match_rules():
"""
Unit tests for the match_rules method.

Просмотреть файл

@ -0,0 +1,858 @@
[
{
"pk": 1,
"model": "addons.addon",
"fields": {
"dev_agreement": false,
"eula": null,
"last_updated": "2011-07-06T13:29:18",
"view_source": true,
"enable_thankyou": false,
"total_downloads": 0,
"premium_type": 0,
"developer_comments": null,
"_current_version": 1,
"average_daily_downloads": 0,
"manifest_url": null,
"admin_review_type": 1,
"the_future": null,
"trusted": false,
"total_contributions": null,
"locale_disambiguation": null,
"target_locale": null,
"guid": null,
"weekly_downloads": 1709,
"support_url": null,
"disabled_by_user": true,
"paypal_id": "",
"average_rating": 0.0,
"wants_contributions": false,
"average_daily_users": 758,
"bayesian_rating": 1.28657399683251,
"share_count": 0,
"ts_slowness": null,
"homepage": null,
"support_email": null,
"public_stats": false,
"status": 4,
"app_domain": null,
"privacy_policy": null,
"is_packaged": false,
"description": null,
"default_locale": "en-US",
"whiteboard": "",
"enable_new_regions": false,
"suggested_amount": null,
"prerelease": false,
"thankyou_note": null,
"admin_review": false,
"auto_repackage": true,
"slug": "user-disabled",
"external_software": false,
"highest_status": 0,
"_latest_version": 1,
"mozilla_contact": "",
"_backup_version": null,
"name": 134567801,
"created": "2014-12-08T06:39:45",
"type": 1,
"vip_app": false,
"icon_type": "",
"annoying": 0,
"modified": "2014-12-08T06:39:45",
"summary": null,
"nomination_message": null,
"site_specific": false,
"charity": null,
"make_public": null,
"total_reviews": 0,
"the_reason": null,
"hotness": 0.0
}
},
{
"pk": 2,
"model": "addons.addon",
"fields": {
"dev_agreement": false,
"eula": null,
"last_updated": "2011-04-28T07:38:27",
"view_source": true,
"enable_thankyou": false,
"total_downloads": 0,
"premium_type": 0,
"developer_comments": null,
"_current_version": 2,
"average_daily_downloads": 0,
"manifest_url": null,
"admin_review_type": 1,
"the_future": null,
"trusted": false,
"total_contributions": null,
"locale_disambiguation": null,
"target_locale": null,
"guid": null,
"weekly_downloads": 1028,
"support_url": null,
"disabled_by_user": false,
"paypal_id": "",
"average_rating": 0.0,
"wants_contributions": false,
"average_daily_users": 1957,
"bayesian_rating": 4.62977323542687,
"share_count": 0,
"ts_slowness": null,
"homepage": null,
"support_email": null,
"public_stats": false,
"status": 5,
"app_domain": null,
"privacy_policy": null,
"is_packaged": false,
"description": null,
"default_locale": "en-US",
"whiteboard": "",
"enable_new_regions": false,
"suggested_amount": null,
"prerelease": false,
"thankyou_note": null,
"admin_review": false,
"auto_repackage": true,
"slug": "admin-disabled",
"external_software": false,
"highest_status": 0,
"_latest_version": 2,
"mozilla_contact": "",
"_backup_version": null,
"name": 134567802,
"created": "2014-12-08T06:40:07",
"type": 1,
"vip_app": false,
"icon_type": "",
"annoying": 0,
"modified": "2014-12-08T06:40:07",
"summary": null,
"nomination_message": null,
"site_specific": false,
"charity": null,
"make_public": null,
"total_reviews": 0,
"the_reason": null,
"hotness": 0.0
}
},
{
"pk": 3,
"model": "addons.addon",
"fields": {
"dev_agreement": false,
"eula": null,
"last_updated": "2011-02-15T11:33:40",
"view_source": true,
"enable_thankyou": false,
"total_downloads": 0,
"premium_type": 0,
"developer_comments": null,
"_current_version": 3,
"average_daily_downloads": 0,
"manifest_url": null,
"admin_review_type": 1,
"the_future": null,
"trusted": false,
"total_contributions": null,
"locale_disambiguation": null,
"target_locale": null,
"guid": null,
"weekly_downloads": 1046,
"support_url": null,
"disabled_by_user": false,
"paypal_id": "",
"average_rating": 0.0,
"wants_contributions": false,
"average_daily_users": 207,
"bayesian_rating": 4.53931552224925,
"share_count": 0,
"ts_slowness": null,
"homepage": null,
"support_email": null,
"public_stats": false,
"status": 1,
"app_domain": null,
"privacy_policy": null,
"is_packaged": false,
"description": null,
"default_locale": "en-US",
"whiteboard": "",
"enable_new_regions": false,
"suggested_amount": null,
"prerelease": false,
"thankyou_note": null,
"admin_review": false,
"auto_repackage": true,
"slug": "addon-7f0dc590528d4800a4afca6f",
"external_software": false,
"highest_status": 0,
"_latest_version": 3,
"mozilla_contact": "",
"_backup_version": null,
"name": 134567803,
"created": "2014-12-08T06:40:16",
"type": 1,
"vip_app": false,
"icon_type": "",
"annoying": 0,
"modified": "2014-12-08T06:40:16",
"summary": null,
"nomination_message": null,
"site_specific": false,
"charity": null,
"make_public": null,
"total_reviews": 0,
"the_reason": null,
"hotness": 0.0
}
},
{
"pk": 4,
"model": "addons.addon",
"fields": {
"dev_agreement": false,
"eula": null,
"last_updated": "2011-05-22T11:06:50",
"view_source": true,
"enable_thankyou": false,
"total_downloads": 0,
"premium_type": 0,
"developer_comments": null,
"_current_version": 4,
"average_daily_downloads": 0,
"manifest_url": null,
"admin_review_type": 1,
"the_future": null,
"trusted": false,
"total_contributions": null,
"locale_disambiguation": null,
"target_locale": null,
"guid": null,
"weekly_downloads": 1967,
"support_url": null,
"disabled_by_user": false,
"paypal_id": "",
"average_rating": 0.0,
"wants_contributions": false,
"average_daily_users": 1756,
"bayesian_rating": 3.48031119110043,
"share_count": 0,
"ts_slowness": null,
"homepage": null,
"support_email": null,
"public_stats": false,
"status": 4,
"app_domain": null,
"privacy_policy": null,
"is_packaged": false,
"description": null,
"default_locale": "en-US",
"whiteboard": "",
"enable_new_regions": false,
"suggested_amount": null,
"prerelease": false,
"thankyou_note": null,
"admin_review": false,
"auto_repackage": true,
"slug": "addon-8ca80804df0e40c090bc7633",
"external_software": false,
"highest_status": 0,
"_latest_version": 4,
"mozilla_contact": "",
"_backup_version": null,
"name": 134567804,
"created": "2014-12-08T06:40:23",
"type": 1,
"vip_app": false,
"icon_type": "",
"annoying": 0,
"modified": "2014-12-08T06:40:23",
"summary": null,
"nomination_message": null,
"site_specific": false,
"charity": null,
"make_public": null,
"total_reviews": 0,
"the_reason": null,
"hotness": 0.0
}
},
{
"pk": 5,
"model": "addons.addon",
"fields": {
"dev_agreement": false,
"eula": null,
"last_updated": "2011-11-25T07:59:20",
"view_source": true,
"enable_thankyou": false,
"total_downloads": 0,
"premium_type": 0,
"developer_comments": null,
"_current_version": 5,
"average_daily_downloads": 0,
"manifest_url": null,
"admin_review_type": 1,
"the_future": null,
"trusted": false,
"total_contributions": null,
"locale_disambiguation": null,
"target_locale": null,
"guid": null,
"weekly_downloads": 476,
"support_url": null,
"disabled_by_user": false,
"paypal_id": "",
"average_rating": 0.0,
"wants_contributions": false,
"average_daily_users": 1282,
"bayesian_rating": 1.0053223682018,
"share_count": 0,
"ts_slowness": null,
"homepage": null,
"support_email": null,
"public_stats": false,
"status": 4,
"app_domain": null,
"privacy_policy": null,
"is_packaged": false,
"description": null,
"default_locale": "en-US",
"whiteboard": "",
"enable_new_regions": false,
"suggested_amount": null,
"prerelease": false,
"thankyou_note": null,
"admin_review": false,
"auto_repackage": true,
"slug": "addon-669081224b354292a051bbb2",
"external_software": false,
"highest_status": 0,
"_latest_version": 5,
"mozilla_contact": "",
"_backup_version": null,
"name": 134567805,
"created": "2014-12-08T06:40:24",
"type": 1,
"vip_app": false,
"icon_type": "",
"annoying": 0,
"modified": "2014-12-08T06:40:24",
"summary": null,
"nomination_message": null,
"site_specific": false,
"charity": null,
"make_public": null,
"total_reviews": 0,
"the_reason": null,
"hotness": 0.0
}
},
{
"pk": 6,
"model": "addons.addon",
"fields": {
"dev_agreement": false,
"eula": null,
"last_updated": "2011-10-13T15:26:57",
"view_source": true,
"enable_thankyou": false,
"total_downloads": 0,
"premium_type": 0,
"developer_comments": null,
"_current_version": 6,
"average_daily_downloads": 0,
"manifest_url": null,
"admin_review_type": 1,
"the_future": null,
"trusted": false,
"total_contributions": null,
"locale_disambiguation": null,
"target_locale": null,
"guid": null,
"weekly_downloads": 1017,
"support_url": null,
"disabled_by_user": false,
"paypal_id": "",
"average_rating": 0.0,
"wants_contributions": false,
"average_daily_users": 1768,
"bayesian_rating": 4.08202535488486,
"share_count": 0,
"ts_slowness": null,
"homepage": null,
"support_email": null,
"public_stats": false,
"status": 4,
"app_domain": null,
"privacy_policy": null,
"is_packaged": false,
"description": null,
"default_locale": "en-US",
"whiteboard": "",
"enable_new_regions": false,
"suggested_amount": null,
"prerelease": false,
"thankyou_note": null,
"admin_review": false,
"auto_repackage": true,
"slug": "addon-bda0117f6cca49e0bc9321a0",
"external_software": false,
"highest_status": 0,
"_latest_version": 6,
"mozilla_contact": "",
"_backup_version": null,
"name": 134567806,
"created": "2014-12-08T06:40:25",
"type": 1,
"vip_app": false,
"icon_type": "",
"annoying": 0,
"modified": "2014-12-08T06:40:25",
"summary": null,
"nomination_message": null,
"site_specific": false,
"charity": null,
"make_public": null,
"total_reviews": 0,
"the_reason": null,
"hotness": 0.0
}
},
{
"pk": 1,
"model": "versions.version",
"fields": {
"_developer_name": "",
"has_info_request": false,
"license": null,
"created": "2014-12-08T06:39:45",
"has_editor_comment": false,
"releasenotes": null,
"source": "",
"approvalnotes": "",
"supported_locales": "",
"modified": "2014-12-08T06:39:45",
"deleted": false,
"version": "0.4",
"version_int": 40000200100,
"reviewed": null,
"nomination": "2014-12-08T06:39:45",
"addon": 1
}
},
{
"pk": 2,
"model": "versions.version",
"fields": {
"_developer_name": "",
"has_info_request": false,
"license": null,
"created": "2014-12-08T06:40:07",
"has_editor_comment": false,
"releasenotes": null,
"source": "",
"approvalnotes": "",
"supported_locales": "",
"modified": "2014-12-08T06:40:07",
"deleted": false,
"version": "1.2",
"version_int": 1020000200100,
"reviewed": null,
"nomination": null,
"addon": 2
}
},
{
"pk": 3,
"model": "versions.version",
"fields": {
"_developer_name": "",
"has_info_request": false,
"license": null,
"created": "2014-12-08T06:40:16",
"has_editor_comment": false,
"releasenotes": null,
"source": "",
"approvalnotes": "",
"supported_locales": "",
"modified": "2014-12-08T06:40:16",
"deleted": false,
"version": "1.9",
"version_int": 1090000200100,
"reviewed": null,
"nomination": "2014-12-08T06:40:16",
"addon": 3
}
},
{
"pk": 4,
"model": "versions.version",
"fields": {
"_developer_name": "",
"has_info_request": false,
"license": null,
"created": "2014-12-08T06:40:23",
"has_editor_comment": false,
"releasenotes": null,
"source": "",
"approvalnotes": "",
"supported_locales": "",
"modified": "2014-12-08T06:40:23",
"deleted": false,
"version": "1.8",
"version_int": 1080000200100,
"reviewed": null,
"nomination": "2014-12-08T06:40:23",
"addon": 4
}
},
{
"pk": 5,
"model": "versions.version",
"fields": {
"_developer_name": "",
"has_info_request": false,
"license": null,
"created": "2014-12-08T06:40:24",
"has_editor_comment": false,
"releasenotes": null,
"source": "",
"approvalnotes": "",
"supported_locales": "",
"modified": "2014-12-08T06:40:24",
"deleted": false,
"version": "1.1",
"version_int": 1010000200100,
"reviewed": null,
"nomination": "2014-12-08T06:40:25",
"addon": 5
}
},
{
"pk": 6,
"model": "versions.version",
"fields": {
"_developer_name": "",
"has_info_request": false,
"license": null,
"created": "2014-12-08T06:40:25",
"has_editor_comment": false,
"releasenotes": null,
"source": "",
"approvalnotes": "",
"supported_locales": "",
"modified": "2014-12-08T06:40:25",
"deleted": false,
"version": "0.3",
"version_int": 30000200100,
"reviewed": null,
"nomination": "2014-12-08T06:40:25",
"addon": 6
}
},
{
"pk": 1,
"model": "versions.applicationsversions",
"fields": {
"application": 1,
"max": 2,
"version": 1,
"min": 1
}
},
{
"pk": 2,
"model": "versions.applicationsversions",
"fields": {
"application": 1,
"max": 2,
"version": 2,
"min": 1
}
},
{
"pk": 3,
"model": "versions.applicationsversions",
"fields": {
"application": 1,
"max": 2,
"version": 3,
"min": 1
}
},
{
"pk": 4,
"model": "versions.applicationsversions",
"fields": {
"application": 1,
"max": 2,
"version": 4,
"min": 1
}
},
{
"pk": 5,
"model": "versions.applicationsversions",
"fields": {
"application": 1,
"max": 2,
"version": 5,
"min": 1
}
},
{
"pk": 6,
"model": "versions.applicationsversions",
"fields": {
"application": 1,
"max": 2,
"version": 6,
"min": 1
}
},
{
"pk": 1,
"model": "applications.appversion",
"fields": {
"version_int": 4009900200100,
"application": 1,
"version": "4.0.99",
"modified": "2014-12-08T06:39:45",
"created": "2014-12-08T06:39:45"
}
},
{
"pk": 2,
"model": "applications.appversion",
"fields": {
"version_int": 5009900200100,
"application": 1,
"version": "5.0.99",
"modified": "2014-12-08T06:39:45",
"created": "2014-12-08T06:39:45"
}
},
{
"pk": 1,
"model": "files.file",
"fields": {
"status": 4,
"codereview": false,
"builder_version": null,
"hash": "",
"binary_components": false,
"created": "2014-12-08T06:39:45",
"modified": "2014-12-08T06:39:45",
"strict_compatibility": false,
"filename": "1-1",
"binary": false,
"platform": 1,
"version": 1,
"reviewed": null,
"jetpack_version": null,
"requires_chrome": false,
"datestatuschanged": "2014-12-08T06:39:45",
"no_restart": false,
"size": 0
}
},
{
"pk": 2,
"model": "files.file",
"fields": {
"status": 4,
"codereview": false,
"builder_version": null,
"hash": "",
"binary_components": false,
"created": "2014-12-08T06:40:07",
"modified": "2014-12-08T06:40:07",
"strict_compatibility": false,
"filename": "2-2",
"binary": false,
"platform": 1,
"version": 2,
"reviewed": null,
"jetpack_version": null,
"requires_chrome": false,
"datestatuschanged": "2014-12-08T06:40:07",
"no_restart": false,
"size": 0
}
},
{
"pk": 3,
"model": "files.file",
"fields": {
"status": 4,
"codereview": false,
"builder_version": null,
"hash": "",
"binary_components": false,
"created": "2014-12-08T06:40:16",
"modified": "2014-12-08T06:40:16",
"strict_compatibility": false,
"filename": "3-3",
"binary": false,
"platform": 1,
"version": 3,
"reviewed": null,
"jetpack_version": null,
"requires_chrome": false,
"datestatuschanged": "2014-12-08T06:40:16",
"no_restart": false,
"size": 0
}
},
{
"pk": 4,
"model": "files.file",
"fields": {
"status": 4,
"codereview": false,
"builder_version": null,
"hash": "",
"binary_components": false,
"created": "2014-12-08T06:40:23",
"modified": "2014-12-08T06:40:23",
"strict_compatibility": false,
"filename": "4-4",
"binary": false,
"platform": 1,
"version": 4,
"reviewed": null,
"jetpack_version": null,
"requires_chrome": false,
"datestatuschanged": "2014-12-08T06:40:23",
"no_restart": false,
"size": 0
}
},
{
"pk": 5,
"model": "files.file",
"fields": {
"status": 4,
"codereview": false,
"builder_version": null,
"hash": "",
"binary_components": false,
"created": "2014-12-08T06:40:24",
"modified": "2014-12-08T06:40:24",
"strict_compatibility": false,
"filename": "5-5",
"binary": false,
"platform": 1,
"version": 5,
"reviewed": null,
"jetpack_version": null,
"requires_chrome": false,
"datestatuschanged": "2014-12-08T06:40:24",
"no_restart": false,
"size": 0
}
},
{
"pk": 6,
"model": "files.file",
"fields": {
"status": 4,
"codereview": false,
"builder_version": null,
"hash": "",
"binary_components": false,
"created": "2014-12-08T06:40:25",
"modified": "2014-12-08T06:40:25",
"strict_compatibility": false,
"filename": "6-6",
"binary": false,
"platform": 1,
"version": 6,
"reviewed": null,
"jetpack_version": null,
"requires_chrome": false,
"datestatuschanged": "2014-12-08T06:40:25",
"no_restart": false,
"size": 0
}
},
{
"pk": 1,
"model": "translations.translation",
"fields": {
"localized_string_clean": null,
"created": "2014-12-08T06:39:45",
"locale": "en-us",
"modified": "2014-12-08T06:39:45",
"id": 134567801,
"localized_string": "user-disabled"
}
},
{
"pk": 2,
"model": "translations.translation",
"fields": {
"localized_string_clean": null,
"created": "2014-12-08T06:40:07",
"locale": "en-us",
"modified": "2014-12-08T06:40:07",
"id": 134567802,
"localized_string": "admin-disabled"
}
},
{
"pk": 3,
"model": "translations.translation",
"fields": {
"localized_string_clean": null,
"created": "2014-12-08T06:40:16",
"locale": "en-us",
"modified": "2014-12-08T06:40:16",
"id": 134567803,
"localized_string": "Addon 7f0dc590528d4800a4afca6fff72cbaa"
}
},
{
"pk": 4,
"model": "translations.translation",
"fields": {
"localized_string_clean": null,
"created": "2014-12-08T06:40:23",
"locale": "en-us",
"modified": "2014-12-08T06:40:23",
"id": 134567804,
"localized_string": "Addon 8ca80804df0e40c090bc763324dd4b5a"
}
},
{
"pk": 5,
"model": "translations.translation",
"fields": {
"localized_string_clean": null,
"created": "2014-12-08T06:40:24",
"locale": "en-us",
"modified": "2014-12-08T06:40:24",
"id": 134567805,
"localized_string": "Addon 669081224b354292a051bbb278642d03"
}
},
{
"pk": 6,
"model": "translations.translation",
"fields": {
"localized_string_clean": null,
"created": "2014-12-08T06:40:25",
"locale": "en-us",
"modified": "2014-12-08T06:40:25",
"id": 134567806,
"localized_string": "Addon bda0117f6cca49e0bc9321a02a612699"
}
}
]

Просмотреть файл

@ -326,7 +326,8 @@ class TestButton(ButtonTest):
eq_(b.fix_link('foo.com'), 'foo.com?collection_id=xxx')
b = self.get_button(collection=collection, src='src')
eq_(b.fix_link('foo.com'), 'foo.com?src=src&collection_id=xxx')
self.assertUrlEqual(b.fix_link('foo.com'),
'foo.com?src=src&collection_id=xxx')
def test_links(self):
self.version.all_files = self.platform_files

Просмотреть файл

@ -2170,7 +2170,6 @@ class TestAddonWatchDisabled(amo.tests.TestCase):
class TestSearchSignals(amo.tests.ESTestCase):
test_es = True
def setUp(self):
super(TestSearchSignals, self).setUp()

Просмотреть файл

@ -4,7 +4,6 @@ Miscellaneous helpers that make Django compatible with AMO.
import threading
import commonware.log
from caching.base import CachingQuerySet
from product_details import product_details
@ -73,6 +72,7 @@ class CachedProperty(object):
value = obj.__dict__.get(self.__name__, _missing)
if value is _missing:
value = self.func(obj)
from caching.base import CachingQuerySet
if isinstance(value, CachingQuerySet):
# Work around a bug in django-cache-machine that
# causes deadlock or infinite recursion if

Просмотреть файл

@ -8,11 +8,12 @@ import uuid
from contextlib import contextmanager
from datetime import datetime, timedelta
from functools import partial, wraps
from urlparse import urlsplit, urlunsplit
from urlparse import parse_qs, urlparse, urlsplit, urlunsplit
from django import forms, http, test
from django.conf import settings
from django.core.cache import cache
from django.core.management import call_command
from django.db.models.signals import post_save
from django.forms.fields import Field
from django.http import SimpleCookie
@ -21,10 +22,9 @@ from django.utils import translation
import caching
import mock
import pytest
import tower
from dateutil.parser import parse as dateutil_parser
from django_nose import FastFixtureTestCase
from nose.exc import SkipTest
from nose.tools import eq_, nottest
from pyquery import PyQuery as pq
from redisutils import mock_redis, reset_redis
@ -39,14 +39,14 @@ import stats.search
from access.models import Group, GroupUser
from addons.models import (Addon, Persona,
update_search_index as addon_update_search_index)
from addons.tasks import unindex_addons
from amo.urlresolvers import get_url_prefix, Prefixer, reverse, set_url_prefix
from addons.tasks import unindex_addons
from applications.models import AppVersion
from bandwagon.models import Collection
from files.models import File
from lib.es.signals import process, reset
from translations.hold import clean_translations
from translations.models import delete_translation, Translation
from translations.models import Translation
from versions.models import ApplicationsVersions, Version
from users.models import RequestUser, UserProfile
@ -143,6 +143,17 @@ def check_selected(expected, links, selected):
check_links(expected, links, verify=True, selected=selected)
def assert_url_equal(url, other, compare_host=False):
"""Compare url paths and query strings."""
parsed = urlparse(url)
parsed_other = urlparse(other)
eq_(parsed.path, parsed_other.path) # Paths are equal.
eq_(parse_qs(parsed.path),
parse_qs(parsed_other.path)) # Params are equal.
if compare_host:
eq_(parsed.netloc, parsed_other.netloc)
class RedisTest(object):
"""Mixin for when you need to mock redis for testing."""
@ -259,7 +270,7 @@ def default_prefixer():
amo.urlresolvers.set_url_prefix(prefixer)
class BaseTestCase(FastFixtureTestCase):
class BaseTestCase(test.TestCase):
"""Base test case that each and every test cases should inherit from."""
def _pre_setup(self):
@ -324,6 +335,11 @@ class BaseTestCase(FastFixtureTestCase):
locale=locale).localized_string,
localized_string)
def assertUrlEqual(self, url, other, compare_host=False,
compare_scheme=False):
"""Compare url paths and query strings."""
assert_url_equal(url, other, compare_host=compare_host)
class TestCase(MockEsMixin, RedisTest, BaseTestCase):
"""Base class for all amo tests."""
@ -559,6 +575,17 @@ class TestCase(MockEsMixin, RedisTest, BaseTestCase):
"""
return pq(pq(html)(template_selector).html())
def assertUrlEqual(self, url, other, compare_host=False,
compare_scheme=False):
"""Compare url paths and query strings."""
parsed = urlparse(url)
parsed_other = urlparse(other)
eq_(parsed.path, parsed_other.path) # Paths are equal.
eq_(parse_qs(parsed.path),
parse_qs(parsed_other.path)) # Params are equal.
if compare_host:
eq_(parsed.netloc, parsed_other.netloc)
class AMOPaths(object):
"""Mixin for getting common AMO Paths."""
@ -760,18 +787,16 @@ def version_factory(file_kw={}, **kw):
return v
@pytest.mark.es_tests
class ESTestCase(TestCase):
"""Base class for tests that require elasticsearch."""
# ES is slow to set up so this uses class setup/teardown. That happens
# outside Django transactions so be careful to clean up afterwards.
test_es = True
mock_es = False
exempt_from_fixture_bundling = True # ES doesn't support bundling (yet?)
@classmethod
def setUpClass(cls):
if not settings.RUN_ES_TESTS:
raise SkipTest('ES disabled')
cls.es = amo.search.get_es(timeout=settings.ES_TIMEOUT)
# The ES setting are set before we call super()
@ -784,9 +809,10 @@ class ESTestCase(TestCase):
try:
cls.es.cluster.health()
except Exception, e:
e.args = tuple([u'%s (it looks like ES is not running, '
'try starting it or set RUN_ES_TESTS=False)'
% e.args[0]] + list(e.args[1:]))
e.args = tuple(
[u"%s (it looks like ES is not running, try starting it or "
u"don't run ES tests: make test_no_es)" % e.args[0]] +
list(e.args[1:]))
raise
cls._SEARCH_ANALYZER_MAP = amo.SEARCH_ANALYZER_MAP
@ -803,28 +829,8 @@ class ESTestCase(TestCase):
@classmethod
def tearDownClass(cls):
try:
if hasattr(cls, '_addons'):
addons = Addon.objects.filter(
pk__in=[a.id for a in cls._addons])
# First delete all the translations.
for addon in addons:
for field in addon._meta.translated_fields:
delete_translation(addon, field.name)
# Then delete the addons themselves.
addons.delete()
unindex_addons([a.id for a in cls._addons])
amo.SEARCH_ANALYZER_MAP = cls._SEARCH_ANALYZER_MAP
finally:
# Make sure we're calling super's tearDownClass even if something
# went wrong in the code above, as otherwise we'd run into bug
# 960598.
super(ESTestCase, cls).tearDownClass()
@classmethod
def setUpIndex(cls):
cls.add_addons()
cls.refresh()
amo.SEARCH_ANALYZER_MAP = cls._SEARCH_ANALYZER_MAP
super(ESTestCase, cls).tearDownClass()
@classmethod
def send(cls):
@ -842,17 +848,6 @@ class ESTestCase(TestCase):
[o.save() for o in model.objects.all()]
cls.refresh(index)
@classmethod
def add_addons(cls):
cls._addons = [
addon_factory(name='user-disabled', disabled_by_user=True),
addon_factory(name='admin-disabled', status=amo.STATUS_DISABLED),
addon_factory(status=amo.STATUS_UNREVIEWED),
addon_factory(),
addon_factory(),
addon_factory(),
]
@classmethod
def empty_index(cls, index):
cls.es.delete_by_query(
@ -861,6 +856,31 @@ class ESTestCase(TestCase):
)
class ESTestCaseWithAddons(ESTestCase):
@classmethod
def setUp(cls):
super(ESTestCaseWithAddons, cls).setUpClass()
# Load the fixture here, to not be overloaded by a child class'
# fixture attribute.
call_command('loaddata', 'addons/base_es')
addon_ids = [1, 2, 3, 4, 5, 6] # From the addons/base_es fixture.
cls._addons = list(Addon.objects.filter(pk__in=addon_ids)
.order_by('id'))
from addons.tasks import index_addons
index_addons(addon_ids)
# Refresh ES.
cls.refresh()
@classmethod
def tearDown(cls):
try:
unindex_addons([a.id for a in cls._addons])
cls._addons = []
finally:
super(ESTestCaseWithAddons, cls).tearDownClass()
class TestXss(TestCase):
fixtures = ['base/addon_3615', 'users/test_backends', ]

Просмотреть файл

@ -9,6 +9,7 @@ from django.utils import translation
import jingo
import mock
import pytest
from nose.tools import eq_, assert_raises, raises
from amo.tests import BaseTestCase
@ -18,6 +19,9 @@ from amo.utils import (cache_ns_key, escape_all, find_language,
to_language)
from product_details import product_details
pytestmark = pytest.mark.django_db
u = u'Ελληνικά'
@ -100,6 +104,9 @@ def test_find_language():
yield check, a, b
@pytest.mark.skipif(
not product_details.last_update,
reason="We don't want to download product_details on travis")
def test_spotcheck():
"""Check a couple product-details files to make sure they're available."""
languages = product_details.languages

Просмотреть файл

@ -5,6 +5,7 @@ from django.conf import settings
from django.core.exceptions import PermissionDenied
import mock
import pytest
from nose.tools import eq_
import amo.tests
@ -14,6 +15,9 @@ from amo.urlresolvers import reverse
from users.models import UserProfile
pytestmark = pytest.mark.django_db
def test_post_required():
f = lambda r: mock.sentinel.response
g = decorators.post_required(f)

Просмотреть файл

@ -3,9 +3,13 @@ import mock
from django.shortcuts import render
import pytest
from nose.tools import eq_
pytestmark = pytest.mark.django_db
@mock.patch('caching.ext.cache._cache_support')
def test_app_in_fragment_cache_key(cache_mock):
cache_mock.return_value = ''

Просмотреть файл

@ -11,6 +11,7 @@ from django.test.utils import override_settings
from django.utils import encoding
import jingo
import pytest
from mock import Mock, patch
from nose.tools import eq_, ok_
from pyquery import PyQuery
@ -22,6 +23,9 @@ from amo.utils import ImageCheck
from versions.models import License
pytestmark = pytest.mark.django_db
def render(s, context={}):
t = jingo.env.from_string(s)
return t.render(context)
@ -188,7 +192,7 @@ def test_urlparams():
# Adding query with existing params.
s = render('{{ base_query|urlparams(frag, sort=sort) }}', c)
eq_(s, '%s?sort=name&x=y#frag' % url)
amo.tests.assert_url_equal(s, '%s?sort=name&x=y#frag' % url)
# Replacing a query param.
s = render('{{ base_query|urlparams(frag, x="z") }}', c)
@ -422,6 +426,7 @@ class TestAnimatedImages(amo.tests.TestCase):
def test_site_nav():
amo.tests.default_prefixer()
r = Mock()
r.APP = amo.FIREFOX
assert 'id="site-nav"' in helpers.site_nav({'request': r})
@ -453,11 +458,19 @@ def test_f():
eq_(render(u'{{ "foo {0}"|f("baré") }}'), u'foo baré')
def test_inline_css():
def test_inline_css(monkeypatch):
jingo.load_helpers()
env = jingo.env
t = env.from_string("{{ inline_css('zamboni/mobile', debug=True) }}")
# Monkeypatch settings.LESS_BIN to not call the less compiler. We don't
# need nor want it in tests.
monkeypatch.setattr(settings, 'LESS_BIN', 'ls')
# Monkeypatch jingo_minify.helpers.is_external to counter-effect the
# autouse fixture in conftest.py.
monkeypatch.setattr(amo.helpers, 'is_external', lambda css: False)
s = t.render()
ok_('background-image: url(/static/img/icons/stars.png);' in s)

Просмотреть файл

@ -1,8 +1,12 @@
from django.utils.translation import trans_real
import pytest
import tower
pytestmark = pytest.mark.django_db
def test_amo_locale_not_in_django():
"""
We load gettext catalogs in this order:

Просмотреть файл

@ -1,4 +1,5 @@
"""Tests for the activitylog."""
import json
from datetime import datetime
from nose.tools import eq_
@ -26,7 +27,7 @@ class LogTest(amo.tests.TestCase):
al = amo.log(amo.LOG.DELETE_REVIEW, 1, a, details=magic)
eq_(al.details, magic)
eq_(al._details, '{"body": "way!", "title": "no"}')
eq_(al._details, json.dumps(magic))
def test_created(self):
"""

Просмотреть файл

@ -3,6 +3,7 @@ import django.contrib.messages as django_messages
from django.contrib.messages.storage import default_storage
from django.http import HttpRequest
import pytest
from jingo import env
from nose.tools import eq_
from tower import ugettext as _
@ -10,6 +11,9 @@ from tower import ugettext as _
from amo.messages import _make_message, info
pytestmark = pytest.mark.django_db
def test_xss():
title = "<script>alert(1)</script>"

Просмотреть файл

@ -3,6 +3,7 @@ from django import http, test
from django.conf import settings
from django.test.client import RequestFactory
import pytest
from commonware.middleware import ScrubRequestOnException
from mock import Mock, patch
from nose.tools import eq_
@ -14,6 +15,9 @@ from amo.urlresolvers import reverse
from zadmin.models import Config, _config_cache
pytestmark = pytest.mark.django_db
class TestMiddleware(amo.tests.TestCase):
def test_no_vary_cookie(self):

Просмотреть файл

@ -1,4 +1,4 @@
import pytest
from mock import Mock
from nose.tools import eq_
@ -9,6 +9,9 @@ from amo import models as context
from addons.models import Addon
pytestmark = pytest.mark.django_db
class ManualOrderTest(TestCase):
fixtures = ('base/addon_3615', 'base/addon_5299_gcal', 'base/addon_40')

Просмотреть файл

@ -1,9 +1,13 @@
import pytest
from mock import Mock
from nose.tools import eq_
from amo.helpers import Paginator
pytestmark = pytest.mark.django_db
def mock_pager(page_number, num_pages, count):
m = Mock()
m.paginator = Mock()

Просмотреть файл

@ -1,7 +1,7 @@
from django.core import paginator
import mock
from nose.tools import eq_
from nose.tools import eq_, ok_
import amo
import amo.search
@ -9,19 +9,13 @@ import amo.tests
from addons.models import Addon
class TestESIndexing(amo.tests.ESTestCase):
mock_es = False
test_es = True
def setUp(self):
super(TestESIndexing, self).setUp()
self.setUpIndex()
self.addCleanup(lambda: self.empty_index('default'))
class TestESIndexing(amo.tests.ESTestCaseWithAddons):
# This needs to be in its own class for data isolation.
def test_indexed_count(self):
# Did all the right addons get indexed?
count = Addon.search().filter(type=1, is_disabled=False).count()
eq_(count, 4) # Created in the setUpClass.
eq_(count,
Addon.objects.filter(disabled_by_user=False,
status__in=amo.VALID_STATUSES).count())
@ -32,7 +26,6 @@ class TestESIndexing(amo.tests.ESTestCase):
class TestNoESIndexing(amo.tests.TestCase):
mock_es = True
def test_no_es(self):
@ -51,13 +44,7 @@ class TestNoESIndexing(amo.tests.TestCase):
assert issubclass(es.__class__, mock.Mock)
class TestES(amo.tests.ESTestCase):
test_es = True
@classmethod
def setUpClass(cls):
super(TestES, cls).setUpClass()
cls.setUpIndex()
class TestES(amo.tests.ESTestCaseWithAddons):
def test_clone(self):
# Doing a filter creates a new ES object.
@ -78,8 +65,12 @@ class TestES(amo.tests.ESTestCase):
def test_and(self):
qs = Addon.search().filter(type=1, category__in=[1, 2])
eq_(qs._build_query()['query']['filtered']['filter'],
{'and': [{'term': {'type': 1}}, {'in': {'category': [1, 2]}}]})
filters = qs._build_query()['query']['filtered']['filter']
# Filters:
# {'and': [{'term': {'type': 1}}, {'in': {'category': [1, 2]}}]}
eq_(filters.keys(), ['and'])
ok_({'term': {'type': 1}} in filters['and'])
ok_({'in': {'category': [1, 2]}} in filters['and'])
def test_query(self):
qs = Addon.search().query(type=1)
@ -93,30 +84,54 @@ class TestES(amo.tests.ESTestCase):
def test_query_multiple_and_range(self):
qs = Addon.search().query(type=1, status__gte=1)
eq_(qs._build_query()['query']['function_score']['query'],
{'bool': {'must': [{'term': {'type': 1}},
{'range': {'status': {'gte': 1}}}, ]}})
query = qs._build_query()['query']['function_score']['query']
# Query:
# {'bool': {'must': [{'term': {'type': 1}},
# {'range': {'status': {'gte': 1}}}, ]}}
eq_(query.keys(), ['bool'])
eq_(query['bool'].keys(), ['must'])
ok_({'term': {'type': 1}} in query['bool']['must'])
ok_({'range': {'status': {'gte': 1}}} in query['bool']['must'])
def test_query_or(self):
qs = Addon.search().query(or_=dict(type=1, status__gte=2))
eq_(qs._build_query()['query']['function_score']['query'],
{'bool': {'should': [{'term': {'type': 1}},
{'range': {'status': {'gte': 2}}}, ]}})
query = qs._build_query()['query']['function_score']['query']
# Query:
# {'bool': {'should': [{'term': {'type': 1}},
# {'range': {'status': {'gte': 2}}}, ]}}
eq_(query.keys(), ['bool'])
eq_(query['bool'].keys(), ['should'])
ok_({'term': {'type': 1}} in query['bool']['should'])
ok_({'range': {'status': {'gte': 2}}} in query['bool']['should'])
def test_query_or_and(self):
qs = Addon.search().query(or_=dict(type=1, status__gte=2), category=2)
eq_(qs._build_query()['query']['function_score']['query'],
{'bool': {'must': [{'term': {'category': 2}},
{'bool': {'should': [
{'term': {'type': 1}},
{'range': {'status': {'gte': 2}}}, ]}}]}})
query = qs._build_query()['query']['function_score']['query']
# Query:
# {'bool': {'must': [{'term': {'category': 2}},
# {'bool': {'should': [
# {'term': {'type': 1}},
# {'range': {'status': {'gte': 2}}}, ]}}]}})
eq_(query.keys(), ['bool'])
eq_(query['bool'].keys(), ['must'])
ok_({'term': {'category': 2}} in query['bool']['must'])
sub_clause = sorted(query['bool']['must'])[0]
eq_(sub_clause.keys(), ['bool'])
eq_(sub_clause['bool'].keys(), ['should'])
ok_({'range': {'status': {'gte': 2}}} in sub_clause['bool']['should'])
ok_({'term': {'type': 1}} in sub_clause['bool']['should'])
def test_query_fuzzy(self):
fuzz = {'boost': 2, 'value': 'woo'}
qs = Addon.search().query(or_=dict(type=1, status__fuzzy=fuzz))
eq_(qs._build_query()['query']['function_score']['query'],
{'bool': {'should': [{'fuzzy': {'status': fuzz}},
{'term': {'type': 1}}, ]}})
query = qs._build_query()['query']['function_score']['query']
# Query:
# {'bool': {'should': [{'fuzzy': {'status': fuzz}},
# {'term': {'type': 1}}, ]}})
eq_(query.keys(), ['bool'])
eq_(query['bool'].keys(), ['should'])
ok_({'term': {'type': 1}} in query['bool']['should'])
ok_({'fuzzy': {'status': fuzz}} in query['bool']['should'])
def test_order_by_desc(self):
qs = Addon.search().order_by('-rating')
@ -137,18 +152,32 @@ class TestES(amo.tests.ESTestCase):
def test_filter_or(self):
qs = Addon.search().filter(type=1).filter(or_=dict(status=1, app=2))
eq_(qs._build_query()['query']['filtered']['filter'],
{'and': [
{'term': {'type': 1}},
{'or': [{'term': {'status': 1}}, {'term': {'app': 2}}]},
]})
filters = qs._build_query()['query']['filtered']['filter']
# Filters:
# {'and': [
# {'term': {'type': 1}},
# {'or': [{'term': {'status': 1}}, {'term': {'app': 2}}]},
# ]}
eq_(filters.keys(), ['and'])
ok_({'term': {'type': 1}} in filters['and'])
or_clause = sorted(filters['and'])[0]
eq_(or_clause.keys(), ['or'])
ok_({'term': {'status': 1}} in or_clause['or'])
ok_({'term': {'app': 2}} in or_clause['or'])
qs = Addon.search().filter(type=1, or_=dict(status=1, app=2))
eq_(qs._build_query()['query']['filtered']['filter'],
{'and': [
{'term': {'type': 1}},
{'or': [{'term': {'status': 1}}, {'term': {'app': 2}}]},
]})
filters = qs._build_query()['query']['filtered']['filter']
# Filters:
# {'and': [
# {'term': {'type': 1}},
# {'or': [{'term': {'status': 1}}, {'term': {'app': 2}}]},
# ]}
eq_(filters.keys(), ['and'])
ok_({'term': {'type': 1}} in filters['and'])
or_clause = sorted(filters['and'])[0]
eq_(or_clause.keys(), ['or'])
ok_({'term': {'status': 1}} in or_clause['or'])
ok_({'term': {'app': 2}} in or_clause['or'])
def test_slice_stop(self):
qs = Addon.search()[:6]
@ -182,35 +211,51 @@ class TestES(amo.tests.ESTestCase):
def test_gte(self):
qs = Addon.search().filter(type__in=[1, 2], status__gte=4)
eq_(qs._build_query()['query']['filtered']['filter'],
{'and': [
{'in': {'type': [1, 2]}},
{'range': {'status': {'gte': 4}}},
]})
filters = qs._build_query()['query']['filtered']['filter']
# Filters:
# {'and': [
# {'in': {'type': [1, 2]}},
# {'range': {'status': {'gte': 4}}},
# ]}
eq_(filters.keys(), ['and'])
ok_({'in': {'type': [1, 2]}} in filters['and'])
ok_({'range': {'status': {'gte': 4}}} in filters['and'])
def test_lte(self):
qs = Addon.search().filter(type__in=[1, 2], status__lte=4)
eq_(qs._build_query()['query']['filtered']['filter'],
{'and': [
{'in': {'type': [1, 2]}},
{'range': {'status': {'lte': 4}}},
]})
filters = qs._build_query()['query']['filtered']['filter']
# Filters:
# {'and': [
# {'in': {'type': [1, 2]}},
# {'range': {'status': {'lte': 4}}},
# ]}
eq_(filters.keys(), ['and'])
ok_({'in': {'type': [1, 2]}} in filters['and'])
ok_({'range': {'status': {'lte': 4}}} in filters['and'])
def test_gt(self):
qs = Addon.search().filter(type__in=[1, 2], status__gt=4)
eq_(qs._build_query()['query']['filtered']['filter'],
{'and': [
{'in': {'type': [1, 2]}},
{'range': {'status': {'gt': 4}}},
]})
filters = qs._build_query()['query']['filtered']['filter']
# Filters:
# {'and': [
# {'in': {'type': [1, 2]}},
# {'range': {'status': {'gt': 4}}},
# ]}
eq_(filters.keys(), ['and'])
ok_({'in': {'type': [1, 2]}} in filters['and'])
ok_({'range': {'status': {'gt': 4}}} in filters['and'])
def test_lt(self):
qs = Addon.search().filter(type__in=[1, 2], status__lt=4)
eq_(qs._build_query()['query']['filtered']['filter'],
{'and': [
{'range': {'status': {'lt': 4}}},
{'in': {'type': [1, 2]}},
]})
filters = qs._build_query()['query']['filtered']['filter']
# Filters:
# {'and': [
# {'range': {'status': {'lt': 4}}},
# {'in': {'type': [1, 2]}},
# ]}
eq_(filters.keys(), ['and'])
ok_({'range': {'status': {'lt': 4}}} in filters['and'])
ok_({'in': {'type': [1, 2]}} in filters['and'])
def test_lt2(self):
qs = Addon.search().filter(status__lt=4)
@ -307,19 +352,35 @@ class TestES(amo.tests.ESTestCase):
qs = (Addon.search().filter(type=1)
.extra(filter={'category__in': [1, 2]}))
eq_(qs._build_query()['query']['filtered']['filter'],
{'and': [{'term': {'type': 1}}, {'in': {'category': [1, 2]}}, ]})
filters = qs._build_query()['query']['filtered']['filter']
# Filters:
# {'and': [{'term': {'type': 1}}, {'in': {'category': [1, 2]}}, ]}
eq_(filters.keys(), ['and'])
ok_({'term': {'type': 1}} in filters['and'])
ok_({'in': {'category': [1, 2]}} in filters['and'])
def test_extra_filter_or(self):
qs = Addon.search().extra(filter={'or_': {'status': 1, 'app': 2}})
eq_(qs._build_query()['query']['filtered']['filter'],
[{'or': [{'term': {'status': 1}}, {'term': {'app': 2}}]}])
filters = qs._build_query()['query']['filtered']['filter']
# Filters:
# [{'or': [{'term': {'status': 1}}, {'term': {'app': 2}}]}])
eq_(len(filters), 1)
eq_(filters[0].keys(), ['or'])
ok_({'term': {'status': 1}} in filters[0]['or'])
ok_({'term': {'app': 2}} in filters[0]['or'])
qs = (Addon.search().filter(type=1)
.extra(filter={'or_': {'status': 1, 'app': 2}}))
eq_(qs._build_query()['query']['filtered']['filter'],
{'and': [{'term': {'type': 1}},
{'or': [{'term': {'status': 1}}, {'term': {'app': 2}}]}]})
filters = qs._build_query()['query']['filtered']['filter']
# Filters:
# {'and': [{'term': {'type': 1}},
# {'or': [{'term': {'status': 1}}, {'term': {'app': 2}}]}]})
eq_(filters.keys(), ['and'])
ok_({'term': {'type': 1}} in filters['and'])
or_clause = sorted(filters['and'])[0]
eq_(or_clause.keys(), ['or'])
ok_({'term': {'status': 1}} in or_clause['or'])
ok_({'term': {'app': 2}} in or_clause['or'])
def test_facet_range(self):
facet = {'range': {'status': [{'lte': 3}, {'gte': 5}]}}
@ -334,13 +395,7 @@ class TestES(amo.tests.ESTestCase):
eq_(qs._build_query()['_source'], ['versions'])
class TestPaginator(amo.tests.ESTestCase):
test_es = True
@classmethod
def setUpClass(cls):
super(TestPaginator, cls).setUpClass()
cls.setUpIndex()
class TestPaginator(amo.tests.ESTestCaseWithAddons):
def setUp(self):
super(TestPaginator, self).setUp()

Просмотреть файл

@ -5,6 +5,7 @@ import tempfile
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage as storage
import pytest
from nose.tools import eq_
from amo.storage_utils import (walk_storage, copy_stored_file,
@ -13,6 +14,9 @@ from amo.tests import BaseTestCase
from amo.utils import rm_local_tmp_dir
pytestmark = pytest.mark.django_db
def test_storage_walk():
tmp = tempfile.mkdtemp()
jn = partial(os.path.join, tmp)

Просмотреть файл

@ -3,6 +3,7 @@ from django.conf import settings
from django.core.urlresolvers import set_script_prefix
from django.test.client import Client, RequestFactory
import pytest
from nose.tools import eq_, assert_not_equal
import amo.tests
@ -11,6 +12,9 @@ from amo.middleware import LocaleAndAppURLMiddleware
from amo.tests import BaseTestCase
pytestmark = pytest.mark.django_db
class MiddlewareTest(BaseTestCase):
"""Tests that the locale and app redirection work properly."""
@ -108,7 +112,7 @@ class MiddlewareTest(BaseTestCase):
def test_get_lang(self):
def check(url, expected):
response = self.process(url)
eq_(response['Location'], expected)
self.assertUrlEqual(response['Location'], expected)
check('/services?lang=fr', '/services')
check('/en-US/firefox?lang=fr', '/fr/firefox/')

Просмотреть файл

@ -1,6 +1,7 @@
import collections
import tempfile
import pytest
from nose.tools import eq_, ok_
@ -10,6 +11,9 @@ from addons.models import Addon
from versions.models import Version
pytestmark = pytest.mark.django_db
class TestAttachTransDict(amo.tests.TestCase):
"""
Tests for attach_trans_dict. For convenience, we re-use Addon model instead

Просмотреть файл

@ -7,6 +7,7 @@ from django.test.client import Client
from django.utils import translation
import jingo
import pytest
from mock import patch
from nose.tools import eq_
from pyquery import PyQuery as pq
@ -29,6 +30,9 @@ from files.tests.test_models import UploadTest
from tags.models import AddonTag, Tag
pytestmark = pytest.mark.django_db
def api_url(x, app='firefox', lang='en-US', version=1.2):
return '/%s/%s/api/%s/%s' % (lang, app, version, x)
@ -434,11 +438,7 @@ class APITest(TestCase):
'preview position="0">',
'<caption>TwitterBar places an icon in the address bar.</caption>',
'full type="image/png">',
urlparams('full/%s/%d.%s' %
('20', 20397, 'png'), src='api', modified=1209834208),
'<thumbnail type="image/png">',
urlparams('thumbs/%s/%d.png' %
('20', 20397), src='api', modified=1209834208),
('<developer_comments>Embrace hug love hug meow meow'
'</developer_comments>'),
'size="100352"',
@ -446,6 +446,19 @@ class APITest(TestCase):
'</homepage>'),
'<support>http://www.chrisfinke.com/addons/twitterbar/</support>')
# For urls with several parameters, we need to use self.assertUrlEqual,
# as the parameters could be in random order. Dicts aren't ordered!
url_needles = {
"full": urlparams(
'{previews}full/20/20397.png'.format(
previews=helpers.user_media_url('previews')),
src='api', modified=1209834208),
"thumbnail": urlparams(
'{previews}thumbs/20/20397.png'.format(
previews=helpers.user_media_url('previews')),
src='api', modified=1209834208),
}
response = make_call('addon/4664', version=1.5)
doc = pq(response.content)
@ -466,6 +479,10 @@ class APITest(TestCase):
for needle in needles:
self.assertContains(response, needle)
for tag, needle in url_needles.iteritems():
url = doc(tag).text()
self.assertUrlEqual(url, needle)
def test_no_contribs_until_approved(self):
Addon.objects.filter(id=4664).update(status=amo.STATUS_LITE)
response = make_call('addon/4664', version=1.5)
@ -588,7 +605,11 @@ class DRFAPITest(DRFMixin, APITest):
"""
Run all APITest tests with DRF.
"""
test_module_url = reverse('api.addon_detail', args=['1.5', 999999])
def setUp(self):
super(DRFAPITest, self).setUp()
self.test_module_url = reverse('api.addon_detail',
args=['1.5', 999999])
class ListTest(TestCase):
@ -688,7 +709,10 @@ class DRFListTest(DRFMixin, ListTest):
"""
Run all ListTest tests with DRF.
"""
test_module_url = reverse('api.list', args=['1.5', 'featured'])
def setUp(self):
super(DRFListTest, self).setUp()
self.test_module_url = reverse('api.list', args=['1.5', 'featured'])
class AddonFilterTest(TestCase):
@ -1268,14 +1292,17 @@ class DRFSearchTest(DRFMixin, SearchTest):
"""
Run all SearchTest tests with DRF.
"""
test_module_url = reverse('api.search', args=['1.5', 'delicious'])
def setUp(self):
super(DRFSearchTest, self).setUp()
self.test_module_url = reverse('api.search', args=['1.5', 'delicious'])
class LanguagePacks(UploadTest):
class LanguagePacksTest(UploadTest):
fixtures = ['addons/listed']
def setUp(self):
super(LanguagePacks, self).setUp()
super(LanguagePacksTest, self).setUp()
self.url = reverse('api.language', args=['1.5'])
self.tb_url = self.url.replace('firefox', 'thunderbird')
self.addon = Addon.objects.get(pk=3723)
@ -1332,8 +1359,11 @@ class LanguagePacks(UploadTest):
]]></strings>"""))
class DRFLanguagePacks(DRFMixin, LanguagePacks):
class DRFLanguagePacksTest(DRFMixin, LanguagePacksTest):
"""
Run all LanguagePack tests with DRF.
"""
test_module_url = reverse('api.language', args=['1.5'])
def setUp(self):
super(DRFLanguagePacksTest, self).setUp()
self.test_module_url = reverse('api.language', args=['1.5'])

Просмотреть файл

@ -4,6 +4,7 @@ import tempfile
from django.conf import settings
import pytest
from nose.tools import eq_
from PIL import Image
@ -11,6 +12,9 @@ from amo.tests.test_helpers import get_image_path
from bandwagon.tasks import resize_icon
pytestmark = pytest.mark.django_db
def test_resize_icon():
somepic = get_image_path('mozilla.png')

Просмотреть файл

@ -8,6 +8,7 @@ import django.test
from django.utils.datastructures import MultiValueDict
from django.utils import encoding
import pytest
from mock import patch, Mock
from nose.tools import eq_
from pyquery import PyQuery as pq
@ -29,6 +30,9 @@ from devhub.models import ActivityLog
from users.models import UserProfile
pytestmark = pytest.mark.django_db
def test_addons_form():
f = forms.AddonsForm(MultiValueDict({'addon': [''],
'addon_comment': ['comment']}))

Просмотреть файл

@ -6,7 +6,7 @@ from xml.dom import minidom
from django.conf import settings
from django.core.cache import cache
from nose.tools import eq_
from nose.tools import eq_, ok_
import amo
import amo.tests
@ -229,7 +229,10 @@ class BlocklistItemTest(XMLAssertsMixin, BlocklistViewTest):
eq_(self.vr()[0].getAttribute('minVersion'), '0.1')
self.item.update(max='0.2')
eq_(self.vr()[0].attributes.keys(), ['minVersion', 'maxVersion'])
keys = self.vr()[0].attributes.keys()
eq_(len(keys), 2)
ok_('minVersion' in keys)
ok_('maxVersion' in keys)
eq_(self.vr()[0].getAttribute('minVersion'), '0.1')
eq_(self.vr()[0].getAttribute('maxVersion'), '0.2')

Просмотреть файл

@ -9,6 +9,7 @@ from django.core.cache import cache
from django.test.utils import override_settings
from django.utils import http as urllib
import pytest
from jingo.helpers import datetime as datetime_filter
import mock
from nose.tools import eq_, assert_raises, nottest
@ -33,6 +34,9 @@ from users.models import UserProfile
from versions.models import Version
pytestmark = pytest.mark.django_db
@nottest
def test_listing_sort(self, sort, key=None, reverse=True, sel_class='opt'):
r = self.client.get(self.url, dict(sort=sort))
@ -58,13 +62,7 @@ def test_default_sort(self, sort, key=None, reverse=True, sel_class='opt'):
test_listing_sort(self, sort, key, reverse, sel_class)
class ExtensionTestCase(amo.tests.ESTestCase):
test_es = True
@classmethod
def setUpClass(cls):
super(ExtensionTestCase, cls).setUpClass()
cls.setUpIndex()
class ExtensionTestCase(amo.tests.ESTestCaseWithAddons):
def setUp(self):
super(ExtensionTestCase, self).setUp()
@ -83,7 +81,6 @@ class TestUpdatedSort(ExtensionTestCase):
class TestESExtensions(ExtensionTestCase):
test_es = True
def test_landing(self):
r = self.client.get(self.url)
@ -847,9 +844,9 @@ class TestSearchToolsPages(BaseSearchToolsTest):
links = doc('#search-tools-sidebar a')
eq_([a.text.strip() for a in links],
['Most Popular', 'Recently Added', # Search Extensions.
'Bookmarks']) # Search Providers.
eq_(sorted([a.text.strip() for a in links]),
sorted(['Most Popular', 'Recently Added', # Search Extensions.
'Bookmarks'])) # Search Providers.
search_ext_url = urlparse(reverse('browse.extensions',
kwargs=dict(category='search-tools')))

Просмотреть файл

@ -56,8 +56,8 @@ class TestIndex(amo.tests.TestCase):
r = self.client.get(self.url)
eq_(r.status_code, 200)
doc = pq(r.content)
eq_(doc('h2.c a').attr('href'),
'{url}?page=1&previous=1'.format(url=self.url))
self.assertUrlEqual(doc('h2.c a').attr('href'),
'{url}?page=1&previous=1'.format(url=self.url))
def test_previous_version_link_with_active_pagination(self):
# The current pagination is not kept when we switch to previous
@ -65,7 +65,8 @@ class TestIndex(amo.tests.TestCase):
r = self.client.get(self.url, {'page': 2, 'type': 'all'})
eq_(r.status_code, 200)
doc = pq(r.content)
eq_(doc('h2.c a').attr('href'),
self.assertUrlEqual(
doc('h2.c a').attr('href'),
'{url}?type=all&page=1&previous=1'.format(url=self.url))

Просмотреть файл

@ -3,6 +3,7 @@ import urllib
from django.utils import translation
import pytest
from mock import Mock
from nose.tools import eq_
from pyquery import PyQuery as pq
@ -17,6 +18,9 @@ from files.models import File
from versions.models import Version
pytestmark = pytest.mark.django_db
def test_dev_page_title():
translation.activate('en-US')
request = Mock()

Просмотреть файл

@ -5,6 +5,7 @@ import tempfile
from django.conf import settings
import mock
import pytest
from nose.tools import eq_
from PIL import Image
@ -17,6 +18,9 @@ from devhub import tasks
from files.models import FileUpload
pytestmark = pytest.mark.django_db
def test_resize_icon_shrink():
""" Image should be shrunk so that the longest side is 32px. """

Просмотреть файл

@ -1361,7 +1361,7 @@ class TestSubmitStep4(TestSubmitBase):
eq_(Image.open(storage.open(dest)).size, (32, 12))
def test_edit_media_uploadedicon_noresize(self):
img = "%s/img/notifications/error.png" % settings.STATIC_ROOT
img = "static/img/notifications/error.png"
src_image = open(img, 'rb')
data = dict(upload_image=src_image)

Просмотреть файл

@ -9,7 +9,7 @@ from django.db.models import Q
from django.test.utils import override_settings
import mock
from nose.tools import eq_
from nose.tools import eq_, ok_
from PIL import Image
from pyquery import PyQuery as pq
@ -594,7 +594,7 @@ class TestEditMedia(TestEdit):
eq_(log[0].action, amo.LOG.CHANGE_ICON.id)
def test_edit_media_uploadedicon_noresize(self):
img = "%s/img/notifications/error.png" % settings.STATIC_ROOT
img = "static/img/notifications/error.png"
src_image = open(img, 'rb')
data = dict(upload_image=src_image)
@ -629,7 +629,7 @@ class TestEditMedia(TestEdit):
eq_(Image.open(storage.open(dest)).size, (48, 48))
def check_image_type(self, url, msg):
img = '%s/js/zamboni/devhub.js' % settings.STATIC_ROOT
img = 'static/js/zamboni/devhub.js'
src_image = open(img, 'rb')
res = self.client.post(url, {'upload_image': src_image})
@ -869,30 +869,37 @@ class TestEditDetails(TestEdit):
description, homepage = map(unicode, [self.addon.description,
self.addon.homepage])
# TODO: description should get fixed up with the form.
fields = ['description', 'name', 'summary']
error = ('Before changing your default locale you must have a name, '
'summary, and description in that locale. '
'You are missing %s.')
missing = lambda f: error % ', '.join(map(repr, f))
'You are missing ')
d = dict(description=description, homepage=homepage,
default_locale='fr')
r = self.client.post(self.details_edit_url, d)
self.assertFormError(r, 'form', None, missing(fields))
# We can't use assertFormError here, because the missing fields are
# stored in a dict, which isn't ordered.
form_error = r.context['form'].non_field_errors()[0]
ok_(form_error.startswith(error))
ok_("'description'" in form_error)
ok_("'name'" in form_error)
ok_("'summary'" in form_error)
# Now we have a name.
self.addon.name = {'fr': 'fr name'}
self.addon.save()
fields.remove('name')
r = self.client.post(self.details_edit_url, d)
self.assertFormError(r, 'form', None, missing(fields))
form_error = r.context['form'].non_field_errors()[0]
ok_(form_error.startswith(error))
ok_("'description'" in form_error)
ok_("'summary'" in form_error)
# Now we have a summary.
self.addon.summary = {'fr': 'fr summary'}
self.addon.save()
fields.remove('summary')
r = self.client.post(self.details_edit_url, d)
self.assertFormError(r, 'form', None, missing(fields))
form_error = r.context['form'].non_field_errors()[0]
ok_(form_error.startswith(error))
ok_("'description'" in form_error)
# Now we're sending an fr description with the form.
d['description_fr'] = 'fr description'

Просмотреть файл

@ -1,7 +1,5 @@
import json
from django.conf import settings
from nose.tools import eq_
from pyquery import PyQuery as pq
@ -44,7 +42,7 @@ class TestSubmitPersona(amo.tests.TestCase):
'and %s pixels tall.' % (w, h)])
def test_img_wrongtype(self):
img = open('%s/js/impala/global.js' % settings.STATIC_ROOT, 'rb')
img = open('static/js/impala/global.js', 'rb')
for url in self.get_img_urls():
r_ajax = self.client.post(url, {'upload_image': img})
r_json = json.loads(r_ajax.content)

Просмотреть файл

@ -211,7 +211,8 @@ class TestModuleAdmin(amo.tests.TestCase):
d = dict(app=amo.FIREFOX.id, module='xx', locales='en-US he he fa fa')
form = DiscoveryModuleForm(d)
assert form.is_valid()
eq_(form.cleaned_data['locales'], 'fa en-US he')
cleaned_locales = form.cleaned_data['locales'].split()
eq_(sorted(cleaned_locales), ['en-US', 'fa', 'he'])
class TestUrls(amo.tests.TestCase):

Просмотреть файл

@ -4,6 +4,7 @@ from datetime import datetime, timedelta
from django.core import mail
from django.core.files.storage import default_storage as storage
import pytest
from mock import Mock, patch
from nose.tools import eq_
from pyquery import PyQuery as pq
@ -23,6 +24,9 @@ from versions.models import Version
from . test_models import create_addon_file
pytestmark = pytest.mark.django_db
REVIEW_ADDON_STATUSES = (amo.STATUS_NOMINATED, amo.STATUS_LITE_AND_NOMINATED,
amo.STATUS_UNREVIEWED)
REVIEW_FILES_STATUSES = (amo.STATUS_BETA, amo.STATUS_NULL, amo.STATUS_PUBLIC,

Просмотреть файл

@ -7,57 +7,14 @@ from datetime import datetime
from django.db import connection, models
from django.db.models import Q
import pytest
from nose.tools import eq_, raises
from amo.tests import BaseTestCase
from editors.sql_model import RawSQLModel
def setup():
sql = """
create table sql_model_test_product (
id int(11) not null auto_increment primary key,
name varchar(255) not null,
created datetime not null
);
create table sql_model_test_cat (
id int(11) not null auto_increment primary key,
name varchar(255) not null
);
create table sql_model_test_product_cat (
id int(11) not null auto_increment primary key,
cat_id int(11) not null references sql_model_test_cat (id),
product_id int(11) not null references sql_model_test_product (id)
);
insert into sql_model_test_product (id, name, created)
values (1, 'defilbrilator', UTC_TIMESTAMP());
insert into sql_model_test_cat (id, name)
values (1, 'safety');
insert into sql_model_test_product_cat (product_id, cat_id)
values (1, 1);
insert into sql_model_test_product (id, name, created)
values (2, 'life jacket', UTC_TIMESTAMP());
insert into sql_model_test_product_cat (product_id, cat_id)
values (2, 1);
insert into sql_model_test_product (id, name, created)
values (3, 'snake skin jacket', UTC_TIMESTAMP());
insert into sql_model_test_cat (id, name)
values (2, 'apparel');
insert into sql_model_test_product_cat (product_id, cat_id)
values (3, 2);
""".split(';')
execute_all(sql)
def teardown():
sql = """
drop table sql_model_test_product_cat;
drop table sql_model_test_cat;
drop table sql_model_test_product;
""".split(';')
execute_all(sql)
def execute_all(statements):
cursor = connection.cursor()
for sql in statements:
@ -107,6 +64,58 @@ class ProductDetail(RawSQLModel):
class TestSQLModel(BaseTestCase):
@pytest.fixture(autouse=True)
def setup(self, request):
sql = """
create table if not exists sql_model_test_product (
id int(11) not null auto_increment primary key,
name varchar(255) not null,
created datetime not null
);
create table if not exists sql_model_test_cat (
id int(11) not null auto_increment primary key,
name varchar(255) not null
);
create table if not exists sql_model_test_product_cat (
id int(11) not null auto_increment primary key,
cat_id int(11) not null references sql_model_test_cat (id),
product_id int(11) not null references sql_model_test_product (id)
);
insert into sql_model_test_product (id, name, created)
values (1, 'defilbrilator', UTC_TIMESTAMP());
insert into sql_model_test_cat (id, name)
values (1, 'safety');
insert into sql_model_test_product_cat (product_id, cat_id)
values (1, 1);
insert into sql_model_test_product (id, name, created)
values (2, 'life jacket', UTC_TIMESTAMP());
insert into sql_model_test_product_cat (product_id, cat_id)
values (2, 1);
insert into sql_model_test_product (id, name, created)
values (3, 'snake skin jacket',UTC_TIMESTAMP());
insert into sql_model_test_cat (id, name)
values (2, 'apparel');
insert into sql_model_test_product_cat (product_id, cat_id)
values (3, 2);
""".split(';')
def teardown():
try:
sql = """
drop table if exists sql_model_test_product_cat;
drop table if exists sql_model_test_cat;
drop table if exists sql_model_test_product;
""".split(';')
execute_all(sql)
except:
pass # No failing here.
teardown()
execute_all(sql)
request.addfinalizer(teardown)
def test_all(self):
eq_(sorted([s.category for s in Summary.objects.all()]),
['apparel', 'safety'])

Просмотреть файл

@ -341,15 +341,15 @@ class TestHome(EditorTest):
doc = pq(self.client.get(url).content)
dts, dds = doc('dt'), doc('dd')
expected = [
('is_flagged', 'True'),
('addon_id', str(review.addon.pk)),
('addon_name', 'test'),
]
for idx, pair in enumerate(expected):
dt, dd = pair
eq_(dts.eq(idx).text(), dt)
eq_(dds.eq(idx).text(), dd)
expected = {
'is_flagged': 'True',
'addon_id': str(review.addon.pk),
'addon_name': 'test',
}
eq_(len(dts), 3)
eq_(len(dds), 3)
for dt, dd in zip(dts, dds):
eq_(dd.text, expected[dt.text])
def test_stats_total(self):
self.approve_reviews()

Просмотреть файл

@ -17,6 +17,7 @@ from django.db import models
from django.dispatch import receiver
from django.template.defaultfilters import slugify
from django.utils.encoding import smart_str
from django.utils.translation import force_text
import commonware
from cache_nuggets.lib import memoize
@ -85,7 +86,7 @@ class File(amo.models.OnChangeMixin, amo.models.ModelBase):
return unicode(self.id)
def get_platform_display(self):
return unicode(amo.PLATFORMS[self.platform].name)
return force_text(amo.PLATFORMS[self.platform].name)
@property
def has_been_validated(self):

Просмотреть файл

@ -13,6 +13,7 @@ from django.conf import settings
from django.test.utils import override_settings
import mock
import pytest
from nose.tools import eq_
import amo
@ -29,6 +30,9 @@ from files.utils import check_xpi_info, JetpackUpgrader, parse_addon, parse_xpi
from versions.models import Version
pytestmark = pytest.mark.django_db
class UploadTest(amo.tests.TestCase, amo.tests.AMOPaths):
"""
Base for tests that mess with file uploads, safely using temp directories.
@ -597,18 +601,19 @@ class TestFileUpload(UploadTest):
assert not upload.validation
assert not upload._escaped_validation
validation = '{"the": "validation"}'
escaped_validation = '{"the": "validation", "ending_tier": 0}'
escaped_validation = {"the": "validation", "ending_tier": 0}
upload.validation = validation
upload.save()
eq_(upload.validation, validation)
eq_(upload._escaped_validation, escaped_validation)
eq_(json.loads(upload._escaped_validation), escaped_validation)
def test_escaped_validation_is_escaped(self):
validation = '''{"the": "valid<script>alert('owned')</script>ation"}'''
escaped_validation = ('''{"the": "valid&lt;script&gt;alert('owned')'''
'''&lt;/script&gt;ation", "ending_tier": 0}''')
escaped_validation = {
"the": "valid&lt;script&gt;alert('owned')&lt;/script&gt;ation",
"ending_tier": 0}
upload = FileUpload.objects.create(validation=validation)
eq_(upload._escaped_validation, escaped_validation)
eq_(json.loads(upload._escaped_validation), escaped_validation)
def test_escaped_validation_ignores_bad_json(self):
upload = FileUpload(validation='wtf')
@ -622,8 +627,8 @@ class TestFileUpload(UploadTest):
upload = FileUpload(validation='{"messages": [{"the": "validation"}]}')
assert not upload._escaped_validation
upload.escaped_validation()
eq_(upload._escaped_validation,
'{"ending_tier": 0, "messages": [{"the": "validation"}]}')
eq_(json.loads(upload._escaped_validation),
{"ending_tier": 0, "messages": [{"the": "validation"}]})
@override_settings(VALIDATOR_MESSAGE_LIMIT=10)
def test_limit_validator_warnings(self):

Просмотреть файл

@ -2,6 +2,7 @@ import json
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
import pytest
from nose.tools import eq_
import amo
@ -13,6 +14,9 @@ from files.utils import find_jetpacks, is_beta, PackageJSONExtractor
from versions.models import Version
pytestmark = pytest.mark.django_db
def test_is_beta():
assert not is_beta('1.2')
assert is_beta('1.2a')

Просмотреть файл

@ -289,14 +289,15 @@ class FilesBase(object):
res = self.client.get(self.file_url())
doc = pq(res.content)
files = doc('#id_left > optgroup > option')
eq_([f.text for f in files],
[str(self.files[0].get_platform_display()),
'%s, %s' % (self.files[1].get_platform_display(),
self.files[2].get_platform_display())])
unreviewed_file = doc('#id_left > optgroup > option.status-unreviewed')
public_file = doc('#id_left > optgroup > option.status-public')
eq_(public_file.text(), str(self.files[0].get_platform_display()))
eq_(unreviewed_file.text(),
'%s, %s' % (self.files[1].get_platform_display(),
self.files[2].get_platform_display()))
eq_(files.eq(0).attr('value'), str(self.files[0].id))
eq_(files.eq(1).attr('value'), str(self.files[1].id))
eq_(public_file.attr('value'), str(self.files[0].id))
eq_(unreviewed_file.attr('value'), str(self.files[1].id))
def test_file_chooser_disabled_coalescing(self):
self.files[1].update(status=amo.STATUS_DISABLED)
@ -304,8 +305,8 @@ class FilesBase(object):
res = self.client.get(self.file_url())
doc = pq(res.content)
files = doc('#id_left > optgroup > option')
eq_(files.eq(1).attr('value'), str(self.files[2].id))
disabled_file = doc('#id_left > optgroup > option.status-disabled')
eq_(disabled_file.attr('value'), str(self.files[2].id))
class TestFileViewer(FilesBase, amo.tests.TestCase):
@ -447,11 +448,11 @@ class TestFileViewer(FilesBase, amo.tests.TestCase):
str(self.files[0].id))
eq_(len(doc('#id_right option[value][selected]')), 0)
@patch.object(amo.PLATFORM_LINUX, 'name', u'所有移动平台')
def test_file_chooser_non_ascii_platform(self):
with self.activate(locale='zh-CN'):
PLATFORM_NAME = u'所有移动平台'
f = self.files[0]
PLATFORM_NAME = u'所有移动平台'
f = self.files[0]
with patch.object(File, 'get_platform_display',
lambda self: PLATFORM_NAME):
eq_(f.get_platform_display(), PLATFORM_NAME)
res = self.client.get(self.file_url())

Просмотреть файл

@ -16,8 +16,8 @@ class CategoriesTests(amo.tests.TestCase):
eq_(len(data), 15)
def test_categories_themes_translations(self):
data = generate_categories()
with self.activate(locale='es'):
data = generate_categories()
ok_(unicode(data[0].name).startswith(u'(español) '))
def test_categories_addons_generation(self):
@ -26,6 +26,6 @@ class CategoriesTests(amo.tests.TestCase):
eq_(len(data), 10)
def test_categories_addons_translations(self):
data = generate_categories(APPS['android'])
with self.activate(locale='es'):
data = generate_categories(APPS['android'])
ok_(unicode(data[0].name).startswith(u'(español) '))

Просмотреть файл

@ -11,7 +11,7 @@ from landfill.generators import _yield_name_and_cat, create_addon, create_theme
from versions.models import Version
class _BaseAddonGeneratorTests(amo.tests.TestCase):
class _BaseAddonGeneratorMixin(object):
def test_tinyset(self):
size = 4
@ -51,23 +51,25 @@ class _BaseAddonGeneratorTests(amo.tests.TestCase):
eq_(len(set(addonname for addonname, cat in data)), size)
class FirefoxAddonGeneratorTests(_BaseAddonGeneratorTests):
class FirefoxAddonGeneratorTests(_BaseAddonGeneratorMixin, amo.tests.TestCase):
app = APPS['firefox']
class ThunderbirdAddonGeneratorTests(_BaseAddonGeneratorTests):
class ThunderbirdAddonGeneratorTests(_BaseAddonGeneratorMixin,
amo.tests.TestCase):
app = APPS['thunderbird']
class AndroidAddonGeneratorTests(_BaseAddonGeneratorTests):
class AndroidAddonGeneratorTests(_BaseAddonGeneratorMixin, amo.tests.TestCase):
app = APPS['android']
class SeamonkeyAddonGeneratorTests(_BaseAddonGeneratorTests):
class SeamonkeyAddonGeneratorTests(_BaseAddonGeneratorMixin,
amo.tests.TestCase):
app = APPS['seamonkey']
class ThemeGeneratorTests(_BaseAddonGeneratorTests):
class ThemeGeneratorTests(_BaseAddonGeneratorMixin, amo.tests.TestCase):
app = None

Просмотреть файл

@ -6,12 +6,16 @@ import urlparse
from django.conf import settings
import mock
import pytest
from mock import Mock
from nose.tools import eq_
import amo.tests
import paypal
pytestmark = pytest.mark.django_db
good_response = (
'responseEnvelope.timestamp='
'2011-01-28T06%3A16%3A33.259-08%3A00&responseEnvelope.ack=Success'

Просмотреть файл

@ -44,8 +44,8 @@ def addon_review_aggregates(*addons, **kw):
stats = dict((x[0], x[1:]) for x in
Review.objects.valid().no_cache().using(using)
.filter(addon__in=addons, is_latest=True)
.values_list('addon')
.annotate(Avg('rating'), Count('addon')))
.annotate(Avg('rating'), Count('addon'))
.values_list('addon', 'rating__avg', 'addon__count'))
for addon in addon_objs:
rating, reviews = stats.get(addon.id, [0, 0])
addon.update(total_reviews=reviews, average_rating=rating)

Просмотреть файл

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import json
from nose.tools import eq_
from pyquery import PyQuery as pq
@ -445,7 +446,7 @@ class TestTranslate(ReviewTest):
review.id, 'fr')
r = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
eq_(r.status_code, 200)
eq_(r.content, '{"body": "oui", "title": "oui"}')
eq_(json.loads(r.content), {"body": "oui", "title": "oui"})
@mock.patch('waffle.switch_is_active', lambda x: True)
@mock.patch('reviews.views.requests')

Просмотреть файл

@ -1,12 +1,16 @@
from django.utils import translation
import jingo
import pytest
from mock import Mock
from nose.tools import eq_
from amo.tests.test_helpers import render
pytestmark = pytest.mark.django_db
def test_showing_helper():
translation.activate('en-US')
tpl = "{{ showing(query, tag, pager) }}"

Просмотреть файл

@ -1,8 +1,12 @@
import pytest
from nose.tools import eq_
from search.utils import floor_version
pytestmark = pytest.mark.django_db
def test_floor_version():
def c(x, y):

Просмотреть файл

@ -6,6 +6,7 @@ from django.http import QueryDict
from django.test.client import RequestFactory
import mock
import pytest
from jingo.helpers import datetime as datetime_filter
from nose.tools import eq_
from pyquery import PyQuery as pq
@ -25,12 +26,10 @@ from users.models import UserProfile
from versions.compare import num as vnum, version_int as vint, MAXVERSION
class TestSearchboxTarget(amo.tests.ESTestCase):
pytestmark = pytest.mark.django_db
@classmethod
def setUpClass(cls):
super(TestSearchboxTarget, cls).setUpClass()
cls.setUpIndex()
class TestSearchboxTarget(amo.tests.ESTestCaseWithAddons):
def check(self, url, placeholder, cat=None, action=None, q=None):
# Checks that we search within addons, personas, collections, etc.
@ -66,7 +65,7 @@ class TestSearchboxTarget(amo.tests.ESTestCase):
'search for add-ons', q='ballin')
class SearchBase(amo.tests.ESTestCase):
class SearchBase(amo.tests.ESTestCaseWithAddons):
def get_results(self, r, sort=True):
"""Return pks of add-ons shown on search results page."""
@ -128,11 +127,6 @@ class SearchBase(amo.tests.ESTestCase):
class TestESSearch(SearchBase):
fixtures = ['base/category']
@classmethod
def setUpClass(cls):
super(TestESSearch, cls).setUpClass()
cls.setUpIndex()
def setUp(self):
super(TestESSearch, self).setUp()
self.url = reverse('search.search')
@ -234,7 +228,12 @@ class TestESSearch(SearchBase):
to = ('?sort=updated&advancedsearch=1&appver=1.0'
'&tag=dearbhair&cat=4%2C84')
r = self.client.get(url + from_)
self.assertRedirects(r, url + to, status_code=301)
eq_(r.status_code, 301)
redirected = r.url
parsed = urlparse.urlparse(redirected)
params = parsed.query
eq_(parsed.path, url)
eq_(urlparse.parse_qs(params), urlparse.parse_qs(to[1:]))
def check_platform_filters(self, platform, expected=None):
r = self.client.get('%s?platform=%s' % (self.url, platform),
@ -617,11 +616,6 @@ class TestESSearch(SearchBase):
class TestPersonaSearch(SearchBase):
@classmethod
def setUpClass(cls):
super(TestPersonaSearch, cls).setUpClass()
cls.setUpIndex()
def setUp(self):
super(TestPersonaSearch, self).setUp()
self.url = urlparams(reverse('search.search'), atype=amo.ADDON_PERSONA)
@ -644,7 +638,7 @@ class TestPersonaSearch(SearchBase):
self._addons.append(amo.tests.addon_factory(type=amo.ADDON_PERSONA,
disabled_by_user=True))
# NOTE: There are also some add-ons in `setUpIndex` for good measure.
# NOTE: There are also some add-ons in the setUpClass for good measure.
self.refresh()
@ -796,6 +790,7 @@ class TestCollectionSearch(SearchBase):
self.refresh()
def test_legacy_redirect(self):
self._generate()
# Ensure `sort=newest` redirects to `sort=created`.
r = self.client.get(urlparams(self.url, sort='newest'))
self.assertRedirects(r, urlparams(self.url, sort='created'), 301)
@ -1067,12 +1062,7 @@ def test_search_redirects():
yield same, qs
class TestAjaxSearch(amo.tests.ESTestCase):
@classmethod
def setUpClass(cls):
super(TestAjaxSearch, cls).setUpClass()
cls.setUpIndex()
class TestAjaxSearch(amo.tests.ESTestCaseWithAddons):
def search_addons(self, url, params, addons=[], types=amo.ADDON_TYPES,
src=None):

Просмотреть файл

@ -1,10 +1,11 @@
from django.conf import settings
from django.utils import translation, encoding
import pytest
import tower
from mock import Mock, patch
from nose.tools import eq_
from pyquery import PyQuery as pq
import tower
from addons.models import Addon
import amo
@ -18,6 +19,9 @@ from sharing import DIGG, FACEBOOK
from users.models import UserProfile
pytestmark = pytest.mark.django_db
class SharingHelpersTestCase(BaseTestCase):
fixtures = ['base/addon_3615']

Просмотреть файл

@ -157,7 +157,6 @@ class TestIndexStats(amo.tests.TestCase):
class TestIndexLatest(amo.tests.ESTestCase):
test_es = True
def test_index_latest(self):
latest = datetime.date.today() - datetime.timedelta(days=5)

Просмотреть файл

@ -88,6 +88,15 @@ class ESStatsTest(StatsTest, amo.tests.ESTestCase):
tasks.index_theme_user_counts(list(user_counts))
self.refresh('stats')
def csv_eq(self, response, expected):
content = csv.DictReader(
# Drop lines that are comments.
filter(lambda row: row[0] != '#', response.content.splitlines()))
expected = csv.DictReader(
# Strip any extra spaces from the expected content.
line.strip() for line in expected.splitlines())
self.assertEqual(tuple(content), tuple(expected))
class TestSeriesSecurity(StatsTest):
"""Tests to make sure all restricted data remains restricted."""
@ -169,21 +178,23 @@ class TestSeriesSecurity(StatsTest):
self._check_it(self.private_views_gen(addon_id=5, format='json'), 403)
class _TestCSVs(StatsTest):
class TestCSVs(ESStatsTest):
"""Tests for CSV output of all known series views."""
first_row = 5
def test_downloads_series(self):
response = self.get_view_response('stats.downloads_series',
group='month', format='csv')
eq_(response.status_code, 200, 'unexpected http status')
rows = list(csv.reader(response.content.split('\n')))
row = rows[self.first_row] # the first row of data after the header
eq_(len(row), 2, 'unexpected row length')
date, count = row
eq_(date, '2009-06-01', 'unexpected date string: %s' % date)
eq_(count, '50', 'unexpected count value: %s' % count)
self.csv_eq(response, """date,count
2009-09-03,10
2009-08-03,10
2009-07-03,10
2009-06-28,10
2009-06-20,10
2009-06-12,10
2009-06-07,10
2009-06-01,10""")
def test_usage_series(self):
for url_args in [self.url_args, self.url_args_theme]:
@ -193,111 +204,82 @@ class _TestCSVs(StatsTest):
group='month', format='csv')
eq_(response.status_code, 200, 'unexpected http status')
rows = list(csv.reader(response.content.split('\n')))
# The first row of data after the header.
row = rows[self.first_row]
eq_(len(row), 2, 'unexpected row length')
date, ave = row
eq_(date, '2009-06-01', 'unexpected date string: %s' % date)
eq_(ave, '83', 'unexpected ADU average: %s' % ave)
self.csv_eq(response, """date,count
2009-06-02,1500
2009-06-01,1000""")
def test_contributions_series(self):
response = self.get_view_response('stats.contributions_series',
group='day', format='csv')
eq_(response.status_code, 200, 'unexpected http status')
rows = list(csv.reader(response.content.split('\n')))
row = rows[self.first_row] # the first row of data after the header
eq_(len(row), 4, 'unexpected row length')
date, total, count, ave = row
eq_(date, '2009-06-02', 'unexpected date string: %s' % date)
eq_(total, '4.98', 'unexpected contribution total: %s' % total)
eq_(count, '2', 'unexpected contribution count: %s' % count)
eq_(ave, '2.49', 'unexpected contribution average: %s' % ave)
self.csv_eq(response, """date,total,count,average
2009-06-02,4.98,2,2.49
2009-06-01,5.0,1,5.0""")
def test_sources_series(self):
response = self.get_view_response('stats.sources_series',
group='month', format='csv')
eq_(response.status_code, 200, 'unexpected http status')
rows = list(csv.reader(response.content.split('\n')))
row = rows[self.first_row] # the first row of data after the header
eq_(len(row), 5, 'unexpected row length')
date, count, source1, source2, source3 = row
eq_(date, '2009-06-01', 'unexpected date string: %s' % date)
eq_(count, '50', 'unexpected count: %s' % count)
eq_(source1, '25', 'unexpected source1 count: %s' % source1)
eq_(source2, '15', 'unexpected source2 count: %s' % source2)
eq_(source3, '10', 'unexpected source3 count: %s' % source3)
self.csv_eq(response, """date,count,search,api
2009-09-03,10,3,2
2009-08-03,10,3,2
2009-07-03,10,3,2
2009-06-28,10,3,2
2009-06-20,10,3,2
2009-06-12,10,3,2
2009-06-07,10,3,2
2009-06-01,10,3,2""")
def test_os_series(self):
response = self.get_view_response('stats.os_series',
group='month', format='csv')
eq_(response.status_code, 200, 'unexpected http status')
rows = list(csv.reader(response.content.split('\n')))
row = rows[self.first_row] # the first row of data after the header
eq_(len(row), 5, 'unexpected row length')
date, count, os1, os2, os3 = row
eq_(date, '2009-06-01', 'unexpected date string: %s' % date)
eq_(count, '83', 'unexpected count: %s' % count)
eq_(os1, '30', 'unexpected os1 count: %s' % os1)
eq_(os2, '30', 'unexpected os2 count: %s' % os2)
eq_(os3, '23', 'unexpected os3 count: %s' % os3)
self.csv_eq(response, """date,count,Windows,Linux
2009-06-02,1500,500,400
2009-06-01,1000,400,300""")
def test_locales_series(self):
response = self.get_view_response('stats.locales_series',
group='month', format='csv')
eq_(response.status_code, 200, 'unexpected http status')
rows = list(csv.reader(response.content.split('\n')))
row = rows[self.first_row] # the first row of data after the header
eq_(len(row), 3, 'unexpected row length')
date, count, locale1 = row
eq_(date, '2009-06-01', 'unexpected date string: %s' % date)
eq_(count, '83', 'unexpected count: %s' % count)
eq_(locale1, '83', 'unexpected locale1 count: %s' % locale1)
self.csv_eq(
response,
"""date,count,English (US) (en-us),"""
"""\xce\x95\xce\xbb\xce\xbb\xce\xb7\xce\xbd\xce\xb9\xce\xba"""
"""\xce\xac (el)
2009-06-02,1500,300,400
2009-06-01,1000,300,400""")
def test_statuses_series(self):
response = self.get_view_response('stats.statuses_series',
group='month', format='csv')
eq_(response.status_code, 200, 'unexpected http status')
rows = list(csv.reader(response.content.split('\n')))
row = rows[self.first_row] # the first row of data after the header
eq_(len(row), 4, 'unexpected row length')
date, count, status1, status2 = row
eq_(date, '2009-06-01', 'unexpected date string: %s' % date)
eq_(count, '83', 'unexpected count: %s' % count)
eq_(status1, '77', 'unexpected status1 count: %s' % status1)
eq_(status2, '6', 'unexpected status2 count: %s' % status2)
self.csv_eq(response, """date,count,userEnabled,userDisabled
2009-06-02,1500,1370,130
2009-06-01,1000,950,50""")
def test_versions_series(self):
response = self.get_view_response('stats.versions_series',
group='month', format='csv')
eq_(response.status_code, 200, 'unexpected http status')
rows = list(csv.reader(response.content.split('\n')))
row = rows[self.first_row] # the first row of data after the header
eq_(len(row), 4, 'unexpected row length')
date, count, version1, version2 = row
eq_(date, '2009-06-01', 'unexpected date string: %s' % date)
eq_(count, '83', 'unexpected count: %s' % count)
eq_(version1, '58', 'unexpected version1 count: %s' % version1)
eq_(version2, '25', 'unexpected version2 count: %s' % version2)
self.csv_eq(response, """date,count,2.0,1.0
2009-06-02,1500,950,550
2009-06-01,1000,800,200""")
def test_apps_series(self):
response = self.get_view_response('stats.apps_series',
group='month', format='csv')
eq_(response.status_code, 200, 'unexpected http status')
rows = list(csv.reader(response.content.split('\n')))
row = rows[self.first_row] # the first row of data after the header
eq_(len(row), 3, 'unexpected row length')
date, count, app1 = row
eq_(date, '2009-06-01', 'unexpected date string: %s' % date)
eq_(count, '83', 'unexpected count: %s' % count)
eq_(app1, '83', 'unexpected app1 count: %s' % app1)
self.csv_eq(response, """date,count,Firefox 4.0
2009-06-02,1500,1500
2009-06-01,1000,1000""")
def test_no_cache(self):
"""Test that the csv or json is not caching, due to lack of data."""
@ -323,10 +305,7 @@ class _TestCSVs(StatsTest):
group='day', format='csv')
eq_(response.status_code, 200)
rows = list(csv.reader(response.content.split('\n')))
eq_(len(rows), 6)
eq_(rows[4], []) # No fields
eq_(rows[self.first_row], []) # There is no data
self.csv_eq(response, """date,count""")
class TestCacheControl(StatsTest):
@ -359,14 +338,6 @@ class TestLayout(StatsTest):
class TestResponses(ESStatsTest):
test_es = True
def csv_eq(self, response, expected):
# Drop the first 4 lines, which contain the header comment.
content = response.content.splitlines()[4:]
# Strip any extra spaces from the expected content.
expected = [line.strip() for line in expected.splitlines()]
self.assertListEqual(content, expected)
def test_usage_json(self):
for url_args in [self.url_args, self.url_args_theme]:

Просмотреть файл

@ -697,7 +697,7 @@ def render_csv(request, addon, stats, fields,
writer.writeheader()
writer.writerows(stats)
fudge_headers(response, list)
fudge_headers(response, stats)
response['Content-Type'] = 'text/csv; charset=utf-8'
return response

Просмотреть файл

@ -2,6 +2,7 @@ from django.conf import settings
from django.utils import translation
import jingo
import pytest
from mock import Mock, patch
from nose.tools import eq_
@ -14,6 +15,9 @@ from translations.models import PurifiedTranslation
from translations.tests.testapp.models import TranslatedModel
pytestmark = pytest.mark.django_db
def super():
jingo.load_helpers()

Просмотреть файл

@ -4,12 +4,14 @@ from contextlib import nested
import django
from django.conf import settings
from django.db import connections, reset_queries
from django.test import TransactionTestCase
from django.test.utils import override_settings
from django.utils import translation
from django.utils.functional import lazy
import jinja2
import multidb
import pytest
from mock import patch
from nose import SkipTest
from nose.tools import eq_
@ -25,6 +27,9 @@ from translations.models import (LinkifiedTranslation, NoLinksTranslation,
TranslationSequence)
pytestmark = pytest.mark.django_db
def ids(qs):
return [o.id for o in qs]
@ -431,7 +436,7 @@ class TranslationTestCase(BaseTestCase):
eq_(obj.name.locale, 'de')
class TranslationMultiDbTests(BaseTestCase):
class TranslationMultiDbTests(TransactionTestCase):
fixtures = ['testapp/test_models.json']
def setUp(self):

Просмотреть файл

@ -1,9 +1,13 @@
import pytest
from nose.tools import eq_
from translations.models import Translation
from translations.utils import transfield_changed, truncate, truncate_text
pytestmark = pytest.mark.django_db
def test_truncate_text():
eq_(truncate_text('foobar', 5), ('...', 0))
eq_(truncate_text('foobar', 5, True), ('fooba...', 0))

Просмотреть файл

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import re
import pytest
from nose.tools import eq_
from pyquery import PyQuery as pq
@ -13,6 +14,9 @@ from users.helpers import (addon_users_list, emaillink, user_data, user_link,
from users.models import UserProfile, RequestUser
pytestmark = pytest.mark.django_db
def test_emaillink():
email = 'me@example.com'
obfuscated = unicode(emaillink(email))

Просмотреть файл

@ -6,6 +6,7 @@ import tempfile
from django.conf import settings
from django.core.files.storage import default_storage as storage
import pytest
from nose.tools import eq_
from PIL import Image
@ -13,6 +14,9 @@ from amo.tests.test_helpers import get_image_path
from users.tasks import delete_photo, resize_photo
pytestmark = pytest.mark.django_db
def test_delete_photo():
dst_path = tempfile.mktemp(suffix='.png',
dir=settings.TMP_PATH)

Просмотреть файл

@ -11,6 +11,7 @@ from django.core.files.base import File as DjangoFile
from django.test.utils import override_settings
import mock
import pytest
from nose.tools import eq_
from pyquery import PyQuery
@ -33,6 +34,9 @@ from versions.compare import (MAXVERSION, version_int, dict_from_int,
version_dict)
pytestmark = pytest.mark.django_db
def test_version_int():
"""Tests that version_int. Corrects our versions."""
eq_(version_int('3.5.0a1pre2'), 3050000001002)

31
conftest.py Normal file
Просмотреть файл

@ -0,0 +1,31 @@
# The following line is needed for all the hackery done to the python path.
import manage # noqa
from django.conf import settings
from django.db.models import loading
import pytest
@pytest.fixture(autouse=True, scope='session')
def _load_testapp():
extra_apps = getattr(settings, 'TEST_INSTALLED_APPS')
if extra_apps:
installed_apps = getattr(settings, 'INSTALLED_APPS')
setattr(settings, 'INSTALLED_APPS', installed_apps + extra_apps)
loading.cache.loaded = False
@pytest.fixture(autouse=True)
def mock_inline_css(monkeypatch):
"""Mock jingo_minify.helpers.is_external: don't break on missing files.
When testing, we don't want nor need the bundled/minified css files, so
pretend that all the css files are external.
Mocking this will prevent amo.helpers.inline_css to believe it should
bundle the css.
"""
import amo.helpers
monkeypatch.setattr(amo.helpers, 'is_external', lambda css: True)

Просмотреть файл

@ -9,6 +9,3 @@ DEBUG_TOOLBAR_PATCH_SETTINGS = False # Prevent DDT from patching the settings.
INTERNAL_IPS = ('127.0.0.1',)
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
# Uncomment to run ES tests (Elasticsearch need to be installed).
#RUN_ES_TESTS = True

Просмотреть файл

@ -4,11 +4,11 @@
Testing
=======
We're using a mix of `Django's Unit Testing`_ and `nose`_
`Selenium`_ for our automated testing. This gives us a lot of power and
flexibility to test all aspects of the site.
We're using a mix of `Django's Unit Testing`_ and `pytest`_ with
`pytest-django`_, and `Selenium`_ for our automated testing. This gives us a
lot of power and flexibility to test all aspects of the site.
Selenium tests are maintained in a seperate `Selenium repository`_.
Selenium tests are maintained in a separate `Selenium repository`_.
Configuration
-------------
@ -19,9 +19,18 @@ has full permissions to modify a database with ``test_`` prepended to it. By
default the database name is ``olympia``, so the test database is
``test_olympia``.
Optionally, in particular if the code you are working on is related to search,
you'll want to run Elasticsearch tests. For this, you need to set the setting
``RUN_ES_TESTS=True``. Obviously, you need Elasticsearch to be installed. See
:ref:`elasticsearch` page for details.
you'll want to run Elasticsearch tests. Obviously, you need Elasticsearch to be
installed. See :ref:`elasticsearch` page for details.
If you don't want to run the Elasticsearch tests, you can use the
``test_no_es`` target in the Makefile::
make test_no_es
On the contrary, if you only want to run Elasticsearch tests, use the
``test_es`` target::
make test_es
Running Tests
@ -29,22 +38,19 @@ Running Tests
To run the whole shebang use::
python manage.py test
py.test
There are a lot of options you can pass to adjust the output. Read `the docs`_
for the full set, but some common ones are:
There are a lot of options you can pass to adjust the output. Read `pytest`_
and `pytest-django`_ docs for the full set, but some common ones are:
* ``--noinput`` tells Django not to ask about creating or destroying test
databases.
* ``--logging-clear-handlers`` tells nose that you don't want to see any
logging output. Without this, our debug logging will spew all over your
console during test runs. This can be useful for debugging, but it's not that
great most of the time. See the docs for more stuff you can do with
:mod:`nose and logging <nose.plugins.logcapture>`.
Our continuous integration server adds some additional flags for other features
(for example, coverage statistics). To see what those commands are check out
the build script at :src:`scripts/build.sh`.
* ``-v`` to provide more verbose information about the test run
* ``-s`` tells pytest to not capture the logging output
* ``--create-db`` tells pytest-django to recreate the database instead of
reusing the one from the previous run
* ``-x --pdb`` to stop on the first failure, and drop into a python debugger
* ``--lf`` to re-run the last test failed
* ``-m test_es`` will only run tests that are marked with the ``test_es`` mark
* ``-k foobar`` will only run tests that contain ``foobar`` in their name
There are a few useful makefile targets that you can use:
@ -63,10 +69,9 @@ To fail and stop running tests on the first failure::
If you wish to add arguments, or run a specific test, overload the variables
(check the Makefile for more information)::
make ARGS='--verbosity 2 olympia.apps.amo.tests.test_url_prefix:MiddlewareTest.test_get_app' test
make test ARGS='-v apps/amo/tests/test_url_prefix.py::MiddlewareTest::test_get_app'
Those targets include some useful options, like the ``--with-id`` which allows
you to re-run only the tests failed from the previous run::
If you wish to re-run only the tests failed from the previous run::
make test_failed
@ -74,11 +79,17 @@ you to re-run only the tests failed from the previous run::
Database Setup
~~~~~~~~~~~~~~
Our test runner will try as hard as it can to skip creating a fresh database
every time. If you really want to make a new database (e.g. when models have
changed), set the environment variable ``FORCE_DB``. ::
Our test runner is configured by default to reuse the database between each
test run. If you really want to make a new database (e.g. when models have
changed), use the ``--create-db`` parameter::
FORCE_DB=true python manage.py test
py.test --create-db
or
::
make test_force_db
Writing Tests
@ -86,6 +97,11 @@ Writing Tests
We support two types of automated tests right now and there are some details
below but remember, if you're confused look at existing tests for examples.
Also, take some time to get familiar with `pytest`_ way of dealing with
dependency injection, which they call `fixtures`_ (which should not be confused
with Django's fixtures). They are very powerful, and can make your tests much
more independent, cleaner, shorter, and more readable.
Unit/Functional Tests
~~~~~~~~~~~~~~~~~~~~~
@ -98,15 +114,12 @@ External calls
Connecting to remote services in tests is not recommended, developers should
mock_ out those calls instead.
To enforce this we run Jenkins with the `nose-blockage`_ plugin, that
will raise errors if you have an HTTP calls in your tests apart from calls to
the whitelisted domains of `127.0.0.1` and `localhost`.
Why Tests Fail
--------------
Tests usually fail for one of two reasons: The code has changed or the data has
changed. An third reason is **time**. Some tests have time-dependent data
usually in the fixtues. For example, some featured items have expiration dates.
usually in the fixtures. For example, some featured items have expiration
dates.
We can usually save our future-selves time by setting these expirations far in
the future.
@ -124,9 +137,9 @@ need to recompile the .mo files manually, for example::
.. _`Django's Unit Testing`: http://docs.djangoproject.com/en/dev/topics/testing
.. _`nose`: https://nose.readthedocs.org/en/latest/
.. _`pytest`: http://pytest.org/latest/
.. _`pytest-django`: https://nose.readthedocs.org/en/latest/
.. _`Selenium`: http://www.seleniumhq.org/
.. _`Selenium repository`: https://github.com/mozilla/Addon-Tests/
.. _`the docs`: http://docs.djangoproject.com/en/dev/topics/testing#id1
.. _mock: http://pypi.python.org/pypi/mock
.. _`nose-blockage`: https://github.com/andymckay/nose-blockage
.. _fixtures: http://pytest.org/latest/fixture.html

Просмотреть файл

@ -91,12 +91,19 @@ Testing with Elasticsearch
--------------------------
All test cases using Elasticsearch should inherit from ``amo.tests.ESTestCase``.
All such tests will be skipped by the test runner unless::
All such tests are marked with the ``es_tests`` pytest_ marker. To run only
those tests::
RUN_ES_TESTS = True
py.test -m es_tests
or
::
make test_es
.. _pytest: http://pytest.org/latest/
This is done as a performance optimization to keep the run time of the test
suite down, unless necessary.
Troubleshooting
---------------

Просмотреть файл

@ -1,7 +1,11 @@
import pytest
from array import array
from nose.tools import eq_
import recommend
from lib import recommend
pytestmark = pytest.mark.django_db
def test_symmetric_diff_count():

Просмотреть файл

@ -1349,11 +1349,6 @@ SIGNING_OMIT_PER_FILE_SIGS = True
# True when the Django app is running from the test suite.
IN_TEST_SUITE = False
# Until bug 753421 gets fixed, we're skipping ES tests. Sad times. I know.
# Flip this on in your local settings or set an environment variable to
# experience the joy of ES tests.
RUN_ES_TESTS = False
# The configuration for the client that speaks to solitude.
# A tuple of the solitude hosts.
SOLITUDE_HOSTS = ('',)

Просмотреть файл

@ -2,6 +2,7 @@ import os
import stat
import tempfile
import pytest
from mock import Mock, patch
from nose import SkipTest
from nose.tools import eq_
@ -18,6 +19,9 @@ from lib.video import ffmpeg, totem
from lib.video.tasks import resize_video
from users.models import UserProfile
pytestmark = pytest.mark.django_db
files = {
'good': os.path.join(os.path.dirname(__file__),
'fixtures/disco-truncated.webm'),

5
pytest.ini Normal file
Просмотреть файл

@ -0,0 +1,5 @@
[pytest]
addopts = --reuse-db --tb=native
python_files=test*.py
markers =
es_tests: mark a test as an elasticsearch test.

Просмотреть файл

@ -2,5 +2,11 @@
# it is not needed for local testing.
-r prod.txt
psutil==0.2.0
execnet==1.2
nose-blockage==0.1.2
psutil==0.2.0
py==1.4.26
pytest==2.6.4
pytest-cache==1.0
pytest-django==2.7.0
pytest-xdist==1.11

Просмотреть файл

@ -37,26 +37,6 @@ pip install -U --exists-action=w --no-deps -q \
-f https://pyrepo.addons.mozilla.org/ \
-r requirements/compiled.txt -r requirements/test.txt
# Create paths we want for addons
if [ ! -d "/tmp/warez" ]; then
mkdir /tmp/warez
fi
if [ ! -d "$LOCALE" ]; then
echo "No locale dir? Cloning..."
svn co http://svn.mozilla.org/addons/trunk/site/app/locale/ $LOCALE
fi
# Install node deps locally.
npm install
export PATH="./node_modules/.bin/:${PATH}"
if [ -z $SET_ES_TESTS ]; then
RUN_ES_TESTS=False
else
RUN_ES_TESTS=True
fi
cat > local_settings.py <<SETTINGS
from settings_ci import *
@ -76,30 +56,10 @@ RUNNING_IN_JENKINS = True
SETTINGS
# Update product details to pull in any changes (namely, 'dbg' locale)
echo "Updating product details..."
python manage.py update_product_details
# Manage statics (collect and compress).
echo "collecting statics..." `date`
python manage.py collectstatic --noinput
echo "building assets..." `date`
python manage.py compress_assets
echo "Starting tests..." `date`
export FORCE_DB='yes sir'
if [[ $3 = 'with-coverage' ]]; then
coverage run manage.py test -v 2 --noinput --logging-clear-handlers --with-xunit
coverage xml $(find apps lib -name '*.py')
else
python manage.py test -v 2 --noinput --logging-clear-handlers --with-xunit --with-blockage --http-whitelist=127.0.0.1,localhost,${ES_HOST}
fi
py.test -v
echo "Building documentation..." `date`

Просмотреть файл

@ -63,8 +63,6 @@ UGLIFY_BIN = path('node_modules/uglify-js/bin/uglifyjs')
# Locally we typically don't run more than 1 elasticsearch node. So we set
# replicas to zero.
ES_DEFAULT_NUM_REPLICAS = 0
# Overload in local_settings.py to run elasticsearch related tests.
RUN_ES_TESTS = False
SITE_URL = 'http://localhost:8000/'
SERVICES_DOMAIN = 'localhost:8000'

Просмотреть файл

@ -1,4 +1,56 @@
import product_details
from settings import * # noqa
LOG_LEVEL = logging.ERROR
RUN_ES_TESTS = True
class MockProductDetails:
"""Main information we need in tests.
We don't want to rely on the product_details that are automatically
downloaded in manage.py for the tests. Also, downloading all the
information is very long, and we don't want that for each test build on
travis for example.
So here's a Mock that can be used instead of the real product_details.
"""
last_update = False
languages = dict((lang, {'native': lang}) for lang in AMO_LANGUAGES)
firefox_versions = {"LATEST_FIREFOX_VERSION": "33.1.1"}
thunderbird_versions = {"LATEST_THUNDERBIRD_VERSION": "31.2.0"}
firefox_history_major_releases = {'1.0': '2004-11-09'}
def __init__(self):
"""Some tests need specifics languages.
This is an excerpt of lib/product_json/languages.json.
"""
self.languages.update({
u'el': {
u'native': u'\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac',
u'English': u'Greek'},
u'hr': {
u'native': u'Hrvatski',
u'English': u'Croatian'},
u'sr': {
u'native': u'\u0421\u0440\u043f\u0441\u043a\u0438',
u'English': u'Serbian'},
u'en-US': {
u'native': u'English (US)',
u'English': u'English (US)'},
u'tr': {
u'native': u'T\xfcrk\xe7e',
u'English': u'Turkish'},
u'cy': {
u'native': u'Cymraeg',
u'English': u'Welsh'},
u'sr-Latn': {
u'native': u'Srpski',
u'English': u'Serbian'}})
product_details.product_details = MockProductDetails()

20
setup.py Normal file
Просмотреть файл

@ -0,0 +1,20 @@
#!/usr/bin/env python
from distutils.core import setup
setup(name='Olympia',
version='0.1dev',
description='This is https://addons.mozilla.org (AMO)',
author='The Mozilla Team',
author_email='amo-developers@mozilla.org',
url='https://addons.mozilla.org/',
packages=['apps', 'lib'],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: Mozilla Public License',
'Operating System :: POSIX',
'Programming Language :: Python',
'Topic :: Internet :: WWW/HTTP :: Browsers',
])

46
tox.ini Normal file
Просмотреть файл

@ -0,0 +1,46 @@
[tox]
envlist = es, addons-devhub-editors, main, flake8, docs
[testenv]
basepython = python2.6
install_command = pip install --no-deps --exists-action=w --download-cache=/tmp/pip-cache --find-links https://pyrepo.addons.mozilla.org/ {opts} {packages}
setenv =
DJANGO_SETTINGS_MODULE=settings_ci
PYTHONPATH=apps
DEB_HOST_MULTIARCH=x86_64-linux-gnu # For M2Crypto on linux.
whitelist_externals =
make
[base]
deps =
-rrequirements/dev.txt
git+git://anonscm.debian.org/collab-maint/m2crypto.git@debian/0.21.1-3#egg=M2Crypto
[testenv:es]
deps = {[base]deps}
commands =
py.test -m es_tests -v {posargs}
[testenv:addons-devhub-editors]
deps = {[base]deps}
commands =
py.test --create-db -m 'not es_tests' -v apps/addons/ apps/devhub/ apps/editors/ {posargs}
[testenv:main]
deps = {[base]deps}
commands =
py.test --create-db -m 'not es_tests' -v --ignore apps/addons/ --ignore apps/devhub/ --ignore apps/editors/ {posargs}
[testenv:flake8]
deps =
flake8
mccabe
pep8
pyflakes
commands = make flake8
[testenv:docs]
deps =
-rrequirements/compiled.txt
-rrequirements/docs.txt
commands = make docs SPHINXOPTS='-nW'

Просмотреть файл

@ -1 +0,0 @@
mkt.wsgi