# -*- coding: utf-8 -*-
import datetime
import json
import os

from django.conf import settings
from django.core.urlresolvers import reverse

import mock
from nose.tools import eq_, ok_
from pyquery import PyQuery as pq

import mkt
from mkt.constants.applications import DEVICE_TYPES
from mkt.files.tests.test_models import UploadTest as BaseUploadTest
from mkt.reviewers.models import EscalationQueue
from mkt.site.fixtures import fixture
from mkt.site.storage_utils import (copy_stored_file, local_storage,
                                    private_storage, public_storage)
from mkt.site.tests import formset, initial, MktPaths, TestCase, user_factory
from mkt.site.tests.test_utils_ import get_image_path
from mkt.submit.decorators import read_dev_agreement_required
from mkt.submit.forms import AppFeaturesForm, NewWebappVersionForm
from mkt.submit.models import AppSubmissionChecklist
from mkt.translations.models import Translation
from mkt.users.models import UserNotification, UserProfile
from mkt.users.notifications import app_surveys
from mkt.webapps.models import AddonDeviceType, AddonUser, AppFeatures, Webapp


class TestSubmit(TestCase):
    fixtures = fixture('user_999')

    def setUp(self):
        self.fi_mock = mock.patch(
            'mkt.developers.tasks.fetch_icon').__enter__()
        self.user = self.get_user()
        self.login(self.user.email)

    def tearDown(self):
        self.fi_mock.__exit__()

    def get_user(self):
        return UserProfile.objects.get(email='regular@mozilla.com')

    def get_url(self, url):
        return reverse('submit.app.%s' % url, args=[self.webapp.app_slug])

    def _test_anonymous(self):
        self.client.logout()
        r = self.client.get(self.url, follow=True)
        self.assertLoginRedirects(r, self.url)

    def _test_progress_display(self, completed, current):
        """Test that the correct steps are highlighted."""
        r = self.client.get(self.url)
        progress = pq(r.content)('#submission-progress')

        # Check the completed steps.
        completed_found = progress.find('.completed')
        for idx, step in enumerate(completed):
            li = completed_found.eq(idx)
            eq_(li.text(), unicode(mkt.APP_STEPS_TITLE[step]))

        # Check that we link back to the Developer Agreement.
        terms_link = progress.find('.terms a')
        if 'terms' in completed:
            eq_(terms_link.attr('href'),
                reverse('mkt.developers.docs', args=['policies', 'agreement']))
        else:
            eq_(terms_link.length, 0)

        # Check the current step.
        eq_(progress.find('.current').text(),
            unicode(mkt.APP_STEPS_TITLE[current]))


class TestProceed(TestSubmit):

    def setUp(self):
        super(TestProceed, self).setUp()
        self.user.update(read_dev_agreement=None)
        self.url = reverse('submit.app')

    def test_is_authenticated(self):
        # Redirect user to Terms.
        r = self.client.get(self.url)
        self.assert3xx(r, reverse('submit.app.terms'))

    def test_is_anonymous(self):
        # Show user to Terms page but with the login prompt.
        self.client.logout()
        r = self.client.get(self.url)
        eq_(r.status_code, 200)
        eq_(r.context['proceed'], True)


class TestTerms(TestSubmit):

    def setUp(self):
        super(TestTerms, self).setUp()
        self.user.update(read_dev_agreement=None)
        self.url = reverse('submit.app.terms')

    def test_anonymous(self):
        self.client.logout()
        r = self.client.get(self.url, follow=True)
        self.assertLoginRedirects(r, self.url)

    def test_jump_to_step(self):
        r = self.client.get(reverse('submit.app'), follow=True)
        self.assert3xx(r, self.url)

    def test_page(self):
        r = self.client.get(self.url)
        eq_(r.status_code, 200)
        doc = pq(r.content)('#submit-terms')
        eq_(doc.length, 1)
        eq_(doc.find('input[name=newsletter]').siblings('label').length, 1,
            'Missing its <label>!')

    def test_progress_display(self):
        self._test_progress_display([], 'terms')

    @mock.patch('basket.subscribe')
    def test_agree(self, subscribe_mock):
        self.client.post(self.url, {'read_dev_agreement': True})
        dt = self.get_user().read_dev_agreement
        self.assertCloseToNow(dt)
        eq_(UserNotification.objects.count(), 0)
        assert not subscribe_mock.called

    @mock.patch('basket.subscribe')
    def test_agree_and_sign_me_up(self, subscribe_mock):
        self.client.post(self.url, {'read_dev_agreement':
                                    datetime.datetime.now(),
                                    'newsletter': True})
        dt = self.get_user().read_dev_agreement
        self.assertCloseToNow(dt)
        eq_(UserNotification.objects.count(), 1)
        notes = UserNotification.objects.filter(user=self.user, enabled=True,
                                                notification_id=app_surveys.id)
        eq_(notes.count(), 1, 'Expected to not be subscribed to newsletter')
        subscribe_mock.assert_called_with(
            self.user.email, 'app-dev', lang='en-US',
            country='restofworld', format='H',
            source_url='http://testserver/developers/submit')

    def test_disagree(self):
        r = self.client.post(self.url)
        eq_(r.status_code, 200)
        eq_(self.user.read_dev_agreement, None)
        eq_(UserNotification.objects.count(), 0)

    def test_read_dev_agreement_required(self):
        f = mock.Mock()
        f.__name__ = 'function'
        request = mock.Mock()
        request.user.read_dev_agreement = None
        request.get_full_path.return_value = self.url
        func = read_dev_agreement_required(f)
        res = func(request)
        assert not f.called
        eq_(res.status_code, 302)
        eq_(res['Location'], reverse('submit.app'))


class TestManifest(TestSubmit):

    def setUp(self):
        super(TestManifest, self).setUp()
        self.user.update(read_dev_agreement=None)
        self.url = reverse('submit.app')

    def _step(self):
        self.user.update(read_dev_agreement=datetime.datetime.now())

    def test_anonymous(self):
        r = self.client.get(self.url, follow=True)
        eq_(r.context['step'], 'terms')

    def test_cannot_skip_prior_step(self):
        r = self.client.get(self.url, follow=True)
        # And we start back at one...
        self.assert3xx(r, reverse('submit.app.terms'))

    def test_jump_to_step(self):
        # I already read the Terms.
        self._step()
        # So jump me to the Manifest step.
        r = self.client.get(reverse('submit.app'), follow=True)
        eq_(r.context['step'], 'manifest')

    def test_legacy_redirects(self):
        def check():
            for before, status in redirects:
                r = self.client.get(before, follow=True)
                self.assert3xx(r, dest, status)

        # I haven't read the dev agreement.
        redirects = (
            ('/developers/submit/', 302),
            ('/developers/submit/app', 302),
            ('/developers/submit/app/terms', 302),
            ('/developers/submit/app/manifest', 302),
        )
        dest = '/developers/submit/terms'
        check()

        # I have read the dev agreement.
        self._step()
        redirects = (
            ('/developers/submit/app', 302),
            ('/developers/submit/app/terms', 302),
            ('/developers/submit/app/manifest', 302),
            ('/developers/submit/manifest', 301),
        )
        dest = '/developers/submit/'
        check()

    def test_page(self):
        self._step()
        r = self.client.get(self.url)
        eq_(r.status_code, 200)
        eq_(pq(r.content)('#upload-file').length, 0)


class UploadAddon(object):

    def post(self, expect_errors=False, data=None):
        if data is None:
            data = {'free_platforms': ['free-desktop']}
        data.update(upload=self.upload.pk)
        response = self.client.post(self.url, data, follow=True)
        eq_(response.status_code, 200)
        if not expect_errors:
            # Show any unexpected form errors.
            if response.context and 'form' in response.context:
                eq_(response.context['form'].errors, {})
        return response


class BaseWebAppTest(BaseUploadTest, UploadAddon, TestCase):
    fixtures = fixture('user_999', 'user_10482')

    def setUp(self):
        super(BaseWebAppTest, self).setUp()
        self.manifest = self.manifest_path('mozball.webapp')
        self.manifest_url = 'http://allizom.org/mozball.webapp'
        self.upload = self.get_upload(abspath=self.manifest,
                                      user=UserProfile.objects.get(pk=999))
        self.upload.update(name=self.manifest_url)
        self.url = reverse('submit.app')
        self.login('regular@mozilla.com')

    def post_addon(self, data=None):
        eq_(Webapp.objects.count(), 0)
        self.post(data=data)
        return Webapp.objects.get()


class TestCreateWebApp(BaseWebAppTest):

    @mock.patch('mkt.developers.tasks.fetch_icon')
    def test_post_app_redirect(self, fi_mock):
        r = self.post()
        webapp = Webapp.objects.get()
        self.assert3xx(r,
                       reverse('submit.app.details', args=[webapp.app_slug]))
        assert fi_mock.delay.called, (
            'The fetch_icon task was expected to be called')

    def test_no_hint(self):
        self.post_addon()
        self.upload = self.get_upload(abspath=self.manifest)
        r = self.client.post(reverse('mkt.developers.upload_manifest'),
                             dict(manifest=self.manifest_url), follow=True)
        eq_(r.status_code, 200)
        assert 'already submitted' not in r.content, (
            'Unexpected helpful error (trap_duplicate)')
        assert 'already exists' not in r.content, (
            'Unexpected validation error (verify_app_domain)')

    def test_no_upload(self):
        data = {'free_platforms': ['free-desktop']}
        res = self.client.post(self.url, data, follow=True)
        eq_(res.context['form'].errors,
            {'upload': NewWebappVersionForm.upload_error})

    @mock.patch('mkt.developers.tasks.fetch_icon')
    def test_bad_upload(self, fi_mock):
        data = {'free_platforms': ['free-desktop'], 'upload': 'foo'}
        res = self.client.post(self.url, data, follow=True)
        eq_(res.context['form'].errors,
            {'upload': NewWebappVersionForm.upload_error})
        assert not fi_mock.delay.called, (
            'The fetch_icon task was not expected to be called')

    def test_hint_for_same_manifest(self):
        self.create_switch(name='webapps-unique-by-domain')
        self.post_addon()
        self.upload = self.get_upload(abspath=self.manifest)
        r = self.client.post(reverse('mkt.developers.upload_manifest'),
                             dict(manifest=self.manifest_url))
        data = json.loads(r.content)
        assert 'Oops' in data['validation']['messages'][0]['message'], (
            'Expected oops')

    def test_no_hint_for_same_manifest_different_author(self):
        self.create_switch(name='webapps-unique-by-domain')
        self.post_addon()

        # Submit same manifest as different user.
        self.login('clouserw@mozilla.com')
        self.upload = self.get_upload(abspath=self.manifest)
        r = self.client.post(reverse('mkt.developers.upload_manifest'),
                             dict(manifest=self.manifest_url))

        data = json.loads(r.content)
        eq_(data['validation']['messages'][0]['message'],
            'An app already exists on this domain; only one app per domain is '
            'allowed.')

    def test_app_from_uploaded_manifest(self):
        addon = self.post_addon()
        eq_(addon.is_packaged, False)
        assert addon.guid is not None, (
            'Expected app to have a UUID assigned to guid')
        eq_(unicode(addon.name), u'MozillaBall ょ')
        eq_(addon.app_slug, u'mozillaball-ょ')
        eq_(addon.description, u'Exciting Open Web development action!')
        eq_(addon.manifest_url, u'http://allizom.org/mozball.webapp')
        eq_(addon.app_domain, u'http://allizom.org')
        eq_(Translation.objects.get(id=addon.description.id, locale='it'),
            u'Azione aperta emozionante di sviluppo di fotoricettore!')
        eq_(addon.latest_version.developer_name, 'Mozilla Labs')
        eq_(addon.latest_version.manifest,
            json.loads(open(self.manifest).read()))

    def test_manifest_with_any_extension(self):
        self.manifest = os.path.join(settings.ROOT, 'mkt', 'developers',
                                     'tests', 'addons', 'mozball.owa')
        self.upload = self.get_upload(abspath=self.manifest,
                                      user=UserProfile.objects.get(pk=999))
        addon = self.post_addon()
        ok_(addon.id)

    def test_version_from_uploaded_manifest(self):
        addon = self.post_addon()
        eq_(addon.latest_version.version, '1.0')

    def test_file_from_uploaded_manifest(self):
        addon = self.post_addon()
        files = addon.latest_version.files.all()
        eq_(len(files), 1)
        eq_(files[0].status, mkt.STATUS_PENDING)

    def test_free(self):
        app = self.post_addon({'free_platforms': ['free-firefoxos']})
        self.assertSetEqual(app.device_types, [mkt.DEVICE_GAIA])
        eq_(app.premium_type, mkt.ADDON_FREE)

    def test_supported_locales(self):
        addon = self.post_addon()
        eq_(addon.default_locale, 'en-US')
        eq_(addon.versions.latest().supported_locales, 'es,it')

    def test_short_locale(self):
        # This manifest has a locale code of "zh" which is in the
        # SHORTER_LANGUAGES setting and should get converted to "zh-CN".
        self.manifest = self.manifest_path('short-locale.webapp')
        self.upload = self.get_upload(abspath=self.manifest,
                                      user=UserProfile.objects.get(pk=999))
        addon = self.post_addon()
        eq_(addon.default_locale, 'zh-CN')
        eq_(addon.versions.latest().supported_locales, 'es')

    def test_unsupported_detail_locale(self):
        # This manifest has a locale code of "en-CA" which is unsupported, so
        # we default to "en-US".
        self.manifest = self.manifest_path('unsupported-default-locale.webapp')
        self.upload = self.get_upload(abspath=self.manifest,
                                      user=UserProfile.objects.get(pk=999))
        addon = self.post_addon()
        eq_(addon.default_locale, 'en-US')
        eq_(addon.versions.latest().supported_locales, 'es,it')

    def test_appfeatures_creation(self):
        addon = self.post_addon(data={
            'free_platforms': ['free-desktop'],
            'has_contacts': 'on'
        })
        features = addon.latest_version.features
        ok_(isinstance(features, AppFeatures))
        field_names = [f.name for f in AppFeaturesForm().all_fields()]
        for field in field_names:
            expected = field == 'has_contacts'
            eq_(getattr(features, field), expected)


class TestCreateWebAppFromManifest(BaseWebAppTest):

    def setUp(self):
        super(TestCreateWebAppFromManifest, self).setUp()
        Webapp.objects.create(app_slug='xxx',
                              app_domain='http://existing-app.com')

    def upload_webapp(self, manifest_url, **post_kw):
        self.upload.update(name=manifest_url)  # Simulate JS upload.
        return self.post(**post_kw)

    def post_manifest(self, manifest_url):
        rs = self.client.post(reverse('mkt.developers.upload_manifest'),
                              dict(manifest=manifest_url))
        if 'json' in rs['content-type']:
            rs = json.loads(rs.content)
        return rs

    def test_duplicate_domain(self):
        self.create_switch(name='webapps-unique-by-domain')
        rs = self.upload_webapp('http://existing-app.com/my.webapp',
                                expect_errors=True)
        eq_(rs.context['form'].errors,
            {'upload':
             ['An app already exists on this domain; only one '
              'app per domain is allowed.']})

    def test_allow_duplicate_domains(self):
        self.upload_webapp('http://existing-app.com/my.webapp')  # No errors.

    def test_duplicate_domain_from_js(self):
        self.create_switch(name='webapps-unique-by-domain')
        data = self.post_manifest('http://existing-app.com/my.webapp')
        eq_(data['validation']['errors'], 1)
        eq_(data['validation']['messages'][0]['message'],
            'An app already exists on this domain; '
            'only one app per domain is allowed.')

    def test_allow_duplicate_domains_from_js(self):
        rs = self.post_manifest('http://existing-app.com/my.webapp')
        eq_(rs.status_code, 302)


class SetupFilesMixin(MktPaths):
    def setup_files(self, filename='mozball.zip'):
        # Local source filename must exist.
        assert os.path.exists(self.packaged_app_path(filename))

        # Remote filename must not be empty.
        assert self.file.filename

        # Original packaged file.
        copy_stored_file(self.packaged_app_path(filename),
                         self.file.file_path,
                         src_storage=local_storage,
                         dst_storage=private_storage)

        # Signed packaged file.
        copy_stored_file(self.packaged_app_path(filename),
                         self.file.signed_file_path,
                         src_storage=local_storage,
                         dst_storage=public_storage)


class BasePackagedAppTest(SetupFilesMixin, BaseUploadTest, UploadAddon,
                          TestCase):
    fixtures = fixture('webapp_337141', 'user_999')

    def setUp(self):
        super(BasePackagedAppTest, self).setUp()
        self.app = Webapp.objects.get(pk=337141)
        self.app.update(is_packaged=True)
        self.version = self.app.latest_version
        self.file = self.version.all_files[0]
        self.file.update(filename='mozball.zip')

        self.upload = self.get_upload(
            abspath=self.package,
            user=UserProfile.objects.get(email='regular@mozilla.com'))
        self.upload.update(name='mozball.zip')
        self.url = reverse('submit.app')
        self.login('regular@mozilla.com')

    @property
    def package(self):
        return self.packaged_app_path('mozball.zip')

    def post_addon(self, data=None):
        eq_(Webapp.objects.count(), 1)
        self.post(data=data)
        return Webapp.objects.order_by('-id')[0]


class TestEscalatePrereleaseWebApp(BasePackagedAppTest):
    def setUp(self):
        super(TestEscalatePrereleaseWebApp, self).setUp()
        user_factory(email=settings.NOBODY_EMAIL_ADDRESS)

    def post(self):
        super(TestEscalatePrereleaseWebApp, self).post(data={
            'free_platforms': ['free-firefoxos'],
            'packaged': True,
        })

    def test_prerelease_permissions_get_escalated(self):
        validation = json.loads(self.upload.validation)
        validation['permissions'] = ['moz-attention']
        self.upload.update(validation=json.dumps(validation))
        eq_(EscalationQueue.objects.count(), 0)
        self.post()
        eq_(EscalationQueue.objects.count(), 1)

    def test_prerelease_permissions_get_escalated_external_app(self):
        validation = json.loads(self.upload.validation)
        validation['permissions'] = ['moz-external-app']
        self.upload.update(validation=json.dumps(validation))
        eq_(EscalationQueue.objects.count(), 0)
        self.post()
        eq_(EscalationQueue.objects.count(), 1)

    def test_normal_permissions_dont_get_escalated(self):
        validation = json.loads(self.upload.validation)
        validation['permissions'] = ['contacts']
        self.upload.update(validation=json.dumps(validation))
        eq_(EscalationQueue.objects.count(), 0)
        self.post()
        eq_(EscalationQueue.objects.count(), 0)


class TestCreatePackagedApp(BasePackagedAppTest):

    @mock.patch('mkt.webapps.models.Webapp.get_cached_manifest')
    def test_post_app_redirect(self, _mock):
        res = self.post()
        webapp = Webapp.objects.order_by('-created')[0]
        self.assert3xx(res,
                       reverse('submit.app.details', args=[webapp.app_slug]))

    @mock.patch('mkt.webapps.models.Webapp.get_cached_manifest')
    @mock.patch('mkt.submit.forms.verify_app_domain')
    def test_app_from_uploaded_package(self, _verify, _mock):
        addon = self.post_addon(
            data={'packaged': True, 'free_platforms': ['free-firefoxos']})
        eq_(addon.latest_version.version, '1.0')
        eq_(addon.is_packaged, True)
        assert addon.guid is not None, (
            'Expected app to have a UUID assigned to guid')
        eq_(unicode(addon.name), u'Packaged MozillaBall ょ')
        eq_(addon.app_slug, u'packaged-mozillaball-ょ')
        eq_(addon.description, u'Exciting Open Web development action!')
        eq_(addon.manifest_url, None)
        eq_(addon.app_domain, 'app://hy.fr')
        eq_(Translation.objects.get(id=addon.description.id, locale='it'),
            u'Azione aperta emozionante di sviluppo di fotoricettore!')
        eq_(addon.latest_version.developer_name, 'Mozilla Labs')

        assert _verify.called, (
            '`verify_app_domain` should be called for packaged apps with '
            'origins.')

    @mock.patch('mkt.webapps.models.Webapp.get_cached_manifest')
    def test_packaged_app_not_unique(self, _mock):
        Webapp.objects.create(is_packaged=True, app_domain='app://hy.fr')
        res = self.post(
            data={'packaged': True, 'free_platforms': ['free-firefoxos']},
            expect_errors=True)
        eq_(res.context['form'].errors, {
            'upload': ['An app already exists on this domain; only one app '
                       'per domain is allowed.']})


class TestDetails(TestSubmit):
    fixtures = fixture('webapp_337141', 'user_999', 'user_10482')

    def setUp(self):
        super(TestDetails, self).setUp()
        self.webapp = self.get_webapp()
        self.webapp.update(status=mkt.STATUS_NULL)
        self.url = reverse('submit.app.details', args=[self.webapp.app_slug])
        self.cat1 = 'books-comics'

    def get_webapp(self):
        return Webapp.objects.get(id=337141)

    def upload_preview(self, image_file=None):
        if not image_file:
            image_file = get_image_path('preview.jpg')
        return self._upload_image(self.webapp.get_dev_url('upload_preview'),
                                  image_file=image_file)

    def upload_icon(self, image_file=None):
        if not image_file:
            image_file = get_image_path('mozilla-sq.png')
        return self._upload_image(self.webapp.get_dev_url('upload_icon'),
                                  image_file=image_file)

    def _upload_image(self, url, image_file):
        with open(image_file, 'rb') as data:
            rp = self.client.post(url, {'upload_image': data})
        eq_(rp.status_code, 200)
        hash_ = json.loads(rp.content)['upload_hash']
        assert hash_, 'No hash: %s' % rp.content
        return hash_

    def _step(self):
        self.user.update(read_dev_agreement=datetime.datetime.now())
        self.cl = AppSubmissionChecklist.objects.create(
            addon=self.webapp,
            terms=True, manifest=True)

        # Associate app with user.
        AddonUser.objects.create(addon=self.webapp, user=self.user)

        # Associate device type with app.
        self.dtype = DEVICE_TYPES.values()[0]
        AddonDeviceType.objects.create(addon=self.webapp,
                                       device_type=self.dtype.id)
        self.device_types = [self.dtype]

        # Associate category with app.
        self.webapp.update(categories=[self.cat1])

    def test_anonymous(self):
        self._test_anonymous()

    def test_resume_later(self):
        self._step()
        self.webapp.appsubmissionchecklist.update(details=True)
        r = self.client.get(reverse('submit.app.resume',
                                    args=[self.webapp.app_slug]))
        self.assert3xx(r, self.webapp.get_dev_url('edit'))

    def test_not_owner(self):
        self._step()
        self.login('clouserw@mozilla.com')
        eq_(self.client.get(self.url).status_code, 403)

    def test_page(self):
        self._step()
        r = self.client.get(self.url)
        eq_(r.status_code, 200)
        eq_(pq(r.content)('#submit-details').length, 1)

    def test_progress_display(self):
        self._step()
        self._test_progress_display(['terms', 'manifest'], 'details')

    def new_preview_formset(self, *args, **kw):
        ctx = self.client.get(self.url).context

        blank = initial(ctx['form_previews'].forms[-1])
        blank.update(**kw)
        return blank

    def preview_formset(self, *args, **kw):
        kw.setdefault('initial_count', 0)
        kw.setdefault('prefix', 'files')

        fs = formset(*[a for a in args] + [self.new_preview_formset()], **kw)
        return dict([(k, '' if v is None else v) for k, v in fs.items()])

    def get_dict(self, **kw):
        data = {
            'app_slug': 'testname',
            'description': 'desc',
            'privacy_policy': 'XXX <script>alert("xss")</script>',
            'homepage': 'http://www.goodreads.com/user/show/7595895-krupa',
            'support_url': 'http://www.goodreads.com/user_challenges/351558',
            'support_email': 'krupa+to+the+rescue@goodreads.com',
            'categories': [self.cat1],
            'publish_type': mkt.PUBLISH_IMMEDIATE,
            'notes': 'yes'
        }
        # Add the required screenshot.
        data.update(self.preview_formset({
            'upload_hash': '<hash>',
            'position': 0
        }))
        data.update(**kw)
        # Remove fields without values.
        data = dict((k, v) for k, v in data.iteritems() if v is not None)
        return data

    def check_dict(self, data=None, expected=None):
        if data is None:
            data = self.get_dict()
        addon = self.get_webapp()

        # Build a dictionary of expected results.
        expected_data = {
            'app_slug': 'testname',
            'description': 'desc',
            'privacy_policy': 'XXX &lt;script&gt;alert("xss")&lt;/script&gt;',
            'publish_type': mkt.PUBLISH_IMMEDIATE,
        }
        if expected:
            expected_data.update(expected)

        self.assertSetEqual(addon.device_types, self.device_types)

        for field, expected in expected_data.iteritems():
            got = unicode(getattr(addon, field))
            expected = unicode(expected)
            eq_(got, expected,
                'Expected %r for %r. Got %r.' % (expected, field, got))

    @mock.patch('mkt.submit.views.record_action')
    def test_success(self, record_action):
        self._step()
        data = self.get_dict()
        r = self.client.post(self.url, data)
        self.assertNoFormErrors(r)
        self.check_dict(data=data)
        self.webapp = self.get_webapp()
        self.assert3xx(r, self.get_url('done'))

        eq_(self.webapp.status, mkt.STATUS_NULL)

        assert record_action.called

    def test_success_paid(self):
        self._step()

        self.webapp = self.get_webapp()
        self.make_premium(self.webapp)
        data = self.get_dict()
        r = self.client.post(self.url, data)
        self.assertNoFormErrors(r)
        self.check_dict(data=data)
        self.webapp = self.get_webapp()
        self.assert3xx(r, self.get_url('done'))

        eq_(self.webapp.status, mkt.STATUS_NULL)
        eq_(self.webapp.highest_status, mkt.STATUS_PENDING)

    def test_success_prefill_device_types_if_empty(self):
        """
        The new submission flow asks for device types at step one.
        This ensures that existing incomplete apps still have device
        compatibility.
        """
        self._step()

        AddonDeviceType.objects.all().delete()
        self.device_types = mkt.DEVICE_TYPES.values()

        data = self.get_dict()
        r = self.client.post(self.url, data)
        self.assertNoFormErrors(r)
        self.check_dict(data=data)
        self.webapp = self.get_webapp()
        self.assert3xx(r, self.get_url('done'))

    def test_success_for_approved(self):
        self._step()

        data = self.get_dict(publish_type=mkt.PUBLISH_PRIVATE)
        r = self.client.post(self.url, data)
        self.assertNoFormErrors(r)

        self.check_dict(data=data,
                        expected={'publish_type': mkt.PUBLISH_PRIVATE})
        self.webapp = self.get_webapp()
        self.assert3xx(r, self.get_url('done'))

    def test_media_types(self):
        self._step()
        res = self.client.get(self.url)
        doc = pq(res.content)
        eq_(doc('.screenshot_upload').attr('data-allowed-types'),
            'image/jpeg|image/png|video/webm')
        eq_(doc('#id_icon_upload').attr('data-allowed-types'),
            'image/jpeg|image/png')

    def test_screenshot(self):
        self._step()
        im_hash = self.upload_preview()
        data = self.get_dict()
        data.update(self.preview_formset({
            'upload_hash': im_hash,
            'position': 0
        }))
        rp = self.client.post(self.url, data)
        eq_(rp.status_code, 302)
        ad = Webapp.objects.get(pk=self.webapp.pk)
        eq_(ad.previews.all().count(), 1)

    def test_icon(self):
        self._step()
        im_hash = self.upload_icon()
        data = self.get_dict()
        data['icon_upload_hash'] = im_hash
        data['icon_type'] = 'image/png'
        rp = self.client.post(self.url, data)
        eq_(rp.status_code, 302)
        ad = self.get_webapp()
        eq_(ad.icon_type, 'image/png')
        for size in mkt.CONTENT_ICON_SIZES:
            fn = '%s-%s.png' % (ad.id, size)
            assert public_storage.exists(
                os.path.join(ad.get_icon_dir(), fn)), ('Expected %s in %s' % (
                    fn, public_storage.listdir(ad.get_icon_dir())[1]))

    def test_screenshot_or_video_required(self):
        self._step()
        data = self.get_dict()
        for k in data:
            if k.startswith('files') and k.endswith('upload_hash'):
                data[k] = ''
        rp = self.client.post(self.url, data)
        eq_(rp.context['form_previews'].non_form_errors(),
            ['You must upload at least one screenshot or video.'])

    def test_unsaved_screenshot(self):
        self._step()
        # If there are form errors we should still pass the previews URIs.
        preview_type = 'video/webm'
        preview_uri = 'moz-filedata:p00p'
        data = self.preview_formset({
            'position': 1,
            'upload_hash': '<hash_one>',
            'unsaved_image_type': preview_type,
            'unsaved_image_data': preview_uri
        })
        r = self.client.post(self.url, data)
        eq_(r.status_code, 200)
        form = pq(r.content)('form')
        eq_(form.find('input[name=files-0-unsaved_image_type]').val(),
            preview_type)
        eq_(form.find('input[name=files-0-unsaved_image_data]').val(),
            preview_uri)

    def test_unique_allowed(self):
        self._step()
        r = self.client.post(self.url, self.get_dict(name=self.webapp.name))
        self.assertNoFormErrors(r)
        app = Webapp.objects.exclude(app_slug=self.webapp.app_slug)[0]
        self.assert3xx(r, reverse('submit.app.done', args=[app.app_slug]))
        eq_(self.get_webapp().status, mkt.STATUS_NULL)

    def test_slug_invalid(self):
        self._step()
        # Submit an invalid slug.
        d = self.get_dict(app_slug='slug!!! aksl23%%')
        r = self.client.post(self.url, d)
        eq_(r.status_code, 200)
        self.assertFormError(
            r, 'form_basic', 'app_slug',
            "Enter a valid 'slug' consisting of letters, numbers, underscores "
            "or hyphens.")

    def test_slug_required(self):
        self._step()
        r = self.client.post(self.url, self.get_dict(app_slug=''))
        eq_(r.status_code, 200)
        self.assertFormError(r, 'form_basic', 'app_slug',
                             'This field is required.')

    def test_description_required(self):
        self._step()
        r = self.client.post(self.url, self.get_dict(description=''))
        eq_(r.status_code, 200)
        self.assertFormError(r, 'form_basic', 'description',
                             'This field is required.')

    def test_privacy_policy_required(self):
        self._step()
        r = self.client.post(self.url, self.get_dict(privacy_policy=None))
        self.assertFormError(r, 'form_basic', 'privacy_policy',
                             'This field is required.')

    def test_clashing_locale(self):
        self.webapp.default_locale = 'de'
        self.webapp.save()
        self._step()
        self.client.cookies['current_locale'] = 'en-us'
        data = self.get_dict(name=None, name_de='Test name',
                             privacy_policy=None,
                             **{'privacy_policy_en-us': 'XXX'})
        r = self.client.post(self.url, data)
        self.assertNoFormErrors(r)

    def test_homepage_url_optional(self):
        self._step()
        r = self.client.post(self.url, self.get_dict(homepage=None))
        self.assertNoFormErrors(r)

    def test_homepage_url_invalid(self):
        self._step()
        r = self.client.post(self.url, self.get_dict(homepage='xxx'))
        self.assertFormError(r, 'form_basic', 'homepage', 'Enter a valid URL.')

    def test_support_url_optional_if_email_present(self):
        self._step()
        r = self.client.post(self.url, self.get_dict(support_url=None))
        self.assertNoFormErrors(r)

    def test_support_url_invalid(self):
        self._step()
        r = self.client.post(self.url, self.get_dict(support_url='xxx'))
        self.assertFormError(r, 'form_basic', 'support_url',
                             'Enter a valid URL.')

    def test_support_email_optional_if_url_present(self):
        self._step()
        r = self.client.post(self.url, self.get_dict(support_email=None))
        self.assertNoFormErrors(r)

    def test_support_email_invalid(self):
        self._step()
        r = self.client.post(self.url, self.get_dict(support_email='xxx'))
        self.assertFormError(r, 'form_basic', 'support_email',
                             'Enter a valid email address.')

    def test_support_need_email_or_url(self):
        self._step()
        res = self.client.post(self.url, self.get_dict(support_email=None,
                                                       support_url=None))
        self.assertFormError(
            res, 'form_basic', 'support',
            'You must provide either a website, an email, or both.')
        ok_(pq(res.content)('#support-fields .error #trans-support_url'))
        ok_(pq(res.content)('#support-fields .error #trans-support_email'))

        # While the inputs will get the error styles, there is no need for an
        # individual error message on each, the hint on the parent is enough.
        eq_(pq(res.content)('#support-fields .error .errorlist').text(), '')

    def test_categories_required(self):
        self._step()
        r = self.client.post(self.url, self.get_dict(categories=[]))
        eq_(r.context['form_cats'].errors['categories'],
            ['This field is required.'])

    def test_categories_max(self):
        self._step()
        eq_(mkt.MAX_CATEGORIES, 2)
        cat2 = 'games'
        cat3 = 'social'
        cats = [self.cat1, cat2, cat3]
        r = self.client.post(self.url, self.get_dict(categories=cats))
        eq_(r.context['form_cats'].errors['categories'],
            ['You can have only 2 categories.'])

    def _post_cats(self, cats):
        self.client.post(self.url, self.get_dict(categories=cats))
        eq_(sorted(self.get_webapp().categories), sorted(cats))

    def test_categories_add(self):
        self._step()
        cat2 = 'games'
        self._post_cats([self.cat1, cat2])

    def test_categories_add_and_remove(self):
        self._step()
        cat2 = 'games'
        self._post_cats([cat2])

    def test_categories_remove(self):
        # Add another category here so it gets added to the initial formset.
        cat2 = 'games'
        self.webapp.update(categories=[self.cat1, cat2])
        self._step()

        # `cat2` should get removed.
        self._post_cats([self.cat1])


class TestDone(TestSubmit):
    fixtures = fixture('user_999', 'webapp_337141')

    def setUp(self):
        super(TestDone, self).setUp()
        self.webapp = self.get_webapp()
        self.url = reverse('submit.app.done', args=[self.webapp.app_slug])

    def get_webapp(self):
        return Webapp.objects.get(id=337141)

    def _step(self, **kw):
        data = dict(addon=self.webapp, terms=True, manifest=True,
                    details=True)
        data.update(kw)
        self.cl = AppSubmissionChecklist.objects.create(**data)
        AddonUser.objects.create(addon=self.webapp, user=self.user)

    def test_anonymous(self):
        self._test_anonymous()

    def test_progress_display(self):
        self._step()
        self._test_progress_display(['terms', 'manifest', 'details'],
                                    'next_steps')

    def test_done(self):
        self._step()
        res = self.client.get(self.url)
        eq_(res.status_code, 200)


class TestNextSteps(TestCase):
    # TODO: Delete this test suite once we deploy IARC.
    fixtures = fixture('user_999', 'webapp_337141')

    def setUp(self):
        self.user = UserProfile.objects.get(email='regular@mozilla.com')
        self.login(self.user.email)
        self.webapp = Webapp.objects.get(id=337141)
        self.webapp.update(status=mkt.STATUS_PENDING)
        self.url = reverse('submit.app.done', args=[self.webapp.app_slug])

    def test_200(self, **kw):
        data = dict(addon=self.webapp, terms=True, manifest=True,
                    details=True)
        data.update(kw)
        self.cl = AppSubmissionChecklist.objects.create(**data)
        AddonUser.objects.create(addon=self.webapp, user=self.user)

        res = self.client.get(self.url)
        eq_(res.status_code, 200)
