from datetime import datetime, timedelta import json from opentech.apply.activity.models import Activity from opentech.apply.determinations.tests.factories import DeterminationFactory from opentech.apply.funds.tests.factories import ( ApplicationSubmissionFactory, ApplicationRevisionFactory, InvitedToProposalFactory, LabSubmissionFactory, SealedRoundFactory, SealedSubmissionFactory, ) from opentech.apply.stream_forms.testing.factories import flatten_for_form from opentech.apply.users.tests.factories import ( ReviewerFactory, StaffFactory, SuperUserFactory, UserFactory, ) from opentech.apply.utils.testing import make_request from opentech.apply.utils.testing.tests import BaseViewTestCase from ..models import ApplicationRevision def prepare_form_data(submission, **kwargs): data = submission.raw_data for field, value in kwargs.items(): # convert named fields into id field_id = submission.field(field).id data[field_id] = value address_field = submission.must_include['address'] address = data.pop(address_field) data.update(**prepare_address(address, address_field)) return data def prepare_address(address, field): address = json.loads(address) address['locality'] = { 'localityname': address.pop('localityname'), 'administrativearea': address.pop('administrativearea'), 'postalcode': address.pop('postalcode'), } address = flatten_for_form(address, field, number=True) return address class BaseSubmissionViewTestCase(BaseViewTestCase): url_name = 'funds:submissions:{}' base_view_name = 'detail' def get_kwargs(self, instance): return {'pk': instance.id} class TestStaffSubmissionView(BaseSubmissionViewTestCase): user_factory = StaffFactory @classmethod def setUpTestData(cls): cls.submission = ApplicationSubmissionFactory() super().setUpTestData() def __setUp__(self): self.refresh(self.submission) def test_can_view_a_submission(self): response = self.get_page(self.submission) self.assertContains(response, self.submission.title) def test_can_view_a_lab_submission(self): submission = LabSubmissionFactory() response = self.get_page(submission) self.assertContains(response, submission.title) def test_can_progress_phase(self): next_status = list(self.submission.get_actions_for_user(self.user))[0][0] self.post_page(self.submission, {'form-submitted-progress_form': '', 'action': next_status}) submission = self.refresh(self.submission) self.assertEqual(submission.status, next_status) def test_redirected_to_determination(self): submission = ApplicationSubmissionFactory(status='concept_review_discussion', workflow_stages=2, lead=self.user) response = self.post_page(submission, {'form-submitted-progress_form': '', 'action': 'invited_to_proposal'}) # Invited for proposal is a a determination, so this will redirect to the determination form. url = self.url_from_pattern('funds:submissions:determinations:form', kwargs={'submission_pk': submission.id}) self.assertRedirects(response, f"{url}?action=invited_to_proposal") def test_new_form_after_progress(self): submission = ApplicationSubmissionFactory(status='invited_to_proposal', workflow_stages=2, lead=self.user) stage = submission.stage DeterminationFactory(submission=submission, accepted=True) request = make_request(self.user, method='get', site=submission.page.get_site()) submission.progress_stage_when_possible(self.user, request) submission = self.refresh(submission) new_stage = submission.stage self.assertNotEqual(stage, new_stage) get_forms = submission.get_from_parent('get_defined_fields') self.assertEqual(submission.form_fields, get_forms(new_stage)) self.assertNotEqual(submission.form_fields, get_forms(stage)) def test_cant_progress_stage_if_not_lead(self): submission = ApplicationSubmissionFactory(status='concept_review_discussion', workflow_stages=2) self.post_page(submission, {'form-submitted-progress_form': '', 'action': 'invited_to_proposal'}) submission = self.refresh(submission) self.assertEqual(submission.status, 'concept_review_discussion') self.assertIsNone(submission.next) def test_not_redirected_if_determination_submitted(self): submission = ApplicationSubmissionFactory(lead=self.user) DeterminationFactory(submission=submission, rejected=True, submitted=True) self.post_page(submission, {'form-submitted-progress_form': '', 'action': 'rejected'}) submission = self.refresh(submission) self.assertEqual(submission.status, 'rejected') def test_not_redirected_if_wrong_determination_selected(self): submission = ApplicationSubmissionFactory(lead=self.user) DeterminationFactory(submission=submission, accepted=True, submitted=True) response = self.post_page(submission, {'form-submitted-progress_form': '', 'action': 'rejected'}) self.assertContains(response, 'you tried to progress') submission = self.refresh(submission) self.assertNotEqual(submission.status, 'accepted') self.assertNotEqual(submission.status, 'rejected') def test_cant_access_edit_button_when_applicant_editing(self): submission = ApplicationSubmissionFactory(status='more_info') response = self.get_page(submission) self.assertNotContains(response, self.url(submission, 'edit', absolute=False)) def test_can_access_edit_button(self): response = self.get_page(self.submission) self.assertContains(response, self.url(self.submission, 'edit', absolute=False)) def test_can_access_edit(self): response = self.get_page(self.submission, 'edit') self.assertContains(response, self.submission.title) def test_previous_and_next_appears_on_page(self): proposal = InvitedToProposalFactory() response = self.get_page(proposal) self.assertContains(response, self.url(proposal.previous, absolute=False)) response = self.get_page(proposal.previous) self.assertContains(response, self.url(proposal, absolute=False)) def test_can_edit_submission(self): old_status = self.submission.status new_title = 'A new Title' data = prepare_form_data(self.submission, title=new_title) response = self.post_page(self.submission, {'submit': True, **data}, 'edit') url = self.url(self.submission) self.assertRedirects(response, url) submission = self.refresh(self.submission) # Staff edits don't affect the status self.assertEqual(old_status, submission.status) self.assertEqual(new_title, submission.title) class TestReviewersUpdateView(BaseSubmissionViewTestCase): user_factory = StaffFactory @classmethod def setUpTestData(cls): super().setUpTestData() cls.staff = StaffFactory.create_batch(4) cls.reviewers = ReviewerFactory.create_batch(4) def post_form(self, submission, staff=list(), reviewers=list()): return self.post_page(submission, { 'form-submitted-reviewer_form': '', 'staff_reviewers': [s.id for s in staff], 'reviewer_reviewers': [r.id for r in reviewers] }) def test_lead_can_add_staff_single(self): submission = ApplicationSubmissionFactory(lead=self.user) self.post_form(submission, self.staff) self.assertCountEqual(submission.reviewers.all(), self.staff) def test_lead_can_remove_staff_single(self): submission = ApplicationSubmissionFactory(lead=self.user, reviewers=self.staff) self.assertCountEqual(submission.reviewers.all(), self.staff) self.post_form(submission, []) self.assertCountEqual(submission.reviewers.all(), []) def test_lead_can_remove_some_staff(self): submission = ApplicationSubmissionFactory(lead=self.user, reviewers=self.staff) self.assertCountEqual(submission.reviewers.all(), self.staff) self.post_form(submission, self.staff[0:2]) self.assertCountEqual(submission.reviewers.all(), self.staff[0:2]) def test_lead_cant_add_reviewers_single(self): submission = ApplicationSubmissionFactory(lead=self.user) self.post_form(submission, reviewers=self.reviewers) self.assertCountEqual(submission.reviewers.all(), []) def test_lead_can_add_staff_and_reviewers_for_proposal(self): submission = InvitedToProposalFactory(lead=self.user) self.post_form(submission, self.staff, self.reviewers) self.assertCountEqual(submission.reviewers.all(), self.staff + self.reviewers) def test_lead_can_remove_staff_and_reviewers_for_proposal(self): submission = InvitedToProposalFactory(lead=self.user, reviewers=self.staff + self.reviewers) self.assertCountEqual(submission.reviewers.all(), self.staff + self.reviewers) self.post_form(submission) self.assertCountEqual(submission.reviewers.all(), []) def test_lead_can_remove_some_staff_and_reviewers_for_proposal(self): submission = InvitedToProposalFactory(lead=self.user, reviewers=self.staff + self.reviewers) self.assertCountEqual(submission.reviewers.all(), self.staff + self.reviewers) self.post_form(submission, self.staff[0:2], self.reviewers[0:2]) self.assertCountEqual(submission.reviewers.all(), self.staff[0:2] + self.reviewers[0:2]) def test_staff_can_add_staff_single(self): submission = ApplicationSubmissionFactory() self.post_form(submission, self.staff) self.assertCountEqual(submission.reviewers.all(), self.staff) def test_staff_can_remove_staff_single(self): submission = ApplicationSubmissionFactory(reviewers=self.staff) self.assertCountEqual(submission.reviewers.all(), self.staff) self.post_form(submission, []) self.assertCountEqual(submission.reviewers.all(), []) def test_staff_cant_add_reviewers_proposal(self): submission = ApplicationSubmissionFactory() self.post_form(submission, reviewers=self.reviewers) self.assertCountEqual(submission.reviewers.all(), []) def test_staff_cant_remove_reviewers_proposal(self): submission = ApplicationSubmissionFactory(reviewers=self.reviewers) self.assertCountEqual(submission.reviewers.all(), self.reviewers) self.post_form(submission, reviewers=[]) self.assertCountEqual(submission.reviewers.all(), self.reviewers) class TestApplicantSubmissionView(BaseSubmissionViewTestCase): user_factory = UserFactory @classmethod def setUpTestData(cls): super().setUpTestData() cls.submission = ApplicationSubmissionFactory(user=cls.user) cls.draft_proposal_submission = InvitedToProposalFactory(user=cls.user, draft=True) def __setUp__(self): self.refresh(self.submission) self.refresh(self.draft_proposal_submission) def test_can_view_own_submission(self): response = self.get_page(self.submission) self.assertContains(response, self.submission.title) def test_sees_latest_draft_if_it_exists(self): draft_revision = ApplicationRevisionFactory(submission=self.submission) self.submission.draft_revision = draft_revision self.submission.save() draft_submission = self.submission.from_draft() response = self.get_page(self.submission) self.assertContains(response, draft_submission.title) def test_cant_view_others_submission(self): submission = ApplicationSubmissionFactory() response = self.get_page(submission) self.assertEqual(response.status_code, 403) def test_get_edit_link_when_editable(self): submission = ApplicationSubmissionFactory(user=self.user, status='more_info') response = self.get_page(submission) self.assertContains(response, 'Edit') self.assertContains(response, self.url(submission, 'edit', absolute=False)) self.assertNotContains(response, 'Congratulations') def test_get_congratulations_draft_proposal(self): response = self.get_page(self.draft_proposal_submission) self.assertContains(response, 'Congratulations') def test_can_edit_own_submission(self): response = self.get_page(self.draft_proposal_submission, 'edit') self.assertContains(response, self.draft_proposal_submission.title) def test_can_submit_submission(self): old_status = self.draft_proposal_submission.status data = prepare_form_data(self.draft_proposal_submission, title='This is different') response = self.post_page(self.draft_proposal_submission, {'submit': True, **data}, 'edit') url = self.url_from_pattern('funds:submissions:detail', kwargs={'pk': self.draft_proposal_submission.id}) self.assertRedirects(response, url) submission = self.refresh(self.draft_proposal_submission) self.assertNotEqual(old_status, submission.status) def test_gets_draft_on_edit_submission(self): draft_revision = ApplicationRevisionFactory(submission=self.draft_proposal_submission) self.draft_proposal_submission.draft_revision = draft_revision self.draft_proposal_submission.save() response = self.get_page(self.draft_proposal_submission, 'edit') self.assertDictEqual(response.context['object'].form_data, draft_revision.form_data) def test_cant_edit_submission_incorrect_state(self): submission = InvitedToProposalFactory(user=self.user) response = self.get_page(submission, 'edit') self.assertEqual(response.status_code, 403) def test_cant_edit_other_submission(self): submission = InvitedToProposalFactory(draft=True) response = self.get_page(submission, 'edit') self.assertEqual(response.status_code, 403) class TestRevisionsView(BaseSubmissionViewTestCase): user_factory = UserFactory def test_create_revisions_on_submit(self): submission = ApplicationSubmissionFactory(status='draft_proposal', workflow_stages=2, user=self.user) old_data = submission.form_data.copy() new_title = 'New title' new_data = prepare_form_data(submission, title=new_title) self.post_page(submission, {'submit': True, **new_data}, 'edit') submission = self.refresh(submission) self.assertEqual(submission.status, 'proposal_discussion') self.assertEqual(submission.revisions.count(), 2) self.assertDictEqual(submission.revisions.last().form_data, old_data) self.assertDictEqual(submission.live_revision.form_data, submission.form_data) self.assertEqual(submission.live_revision.author, self.user) self.assertEqual(submission.title, new_title) def test_dont_update_live_revision_on_save(self): submission = ApplicationSubmissionFactory(status='draft_proposal', workflow_stages=2, user=self.user) old_data = submission.form_data.copy() new_data = prepare_form_data(submission, title='New title') self.post_page(submission, {'save': True, **new_data}, 'edit') submission = self.refresh(submission) self.assertEqual(submission.status, 'draft_proposal') self.assertEqual(submission.revisions.count(), 2) self.assertDictEqual(submission.draft_revision.form_data, submission.from_draft().form_data) self.assertEqual(submission.draft_revision.author, self.user) self.assertDictEqual(submission.live_revision.form_data, old_data) def test_existing_draft_edit_and_submit(self): submission = ApplicationSubmissionFactory(status='draft_proposal', workflow_stages=2, user=self.user) draft_data = prepare_form_data(submission, title='A new title') self.post_page(submission, {'save': True, **draft_data}, 'edit') submission = self.refresh(submission) newer_title = 'Newer title' draft_data = prepare_form_data(submission, title=newer_title) self.post_page(submission, {'submit': True, **draft_data}, 'edit') submission = self.refresh(submission) self.maxDiff = None self.assertEqual(submission.revisions.count(), 2) self.assertDictEqual(submission.draft_revision.form_data, submission.from_draft().form_data) self.assertDictEqual(submission.live_revision.form_data, submission.form_data) self.assertEqual(submission.title, newer_title) class TestRevisionCompare(BaseSubmissionViewTestCase): base_view_name = 'revisions:compare' user_factory = StaffFactory def get_kwargs(self, instance): return { 'submission_pk': instance.pk, 'to': instance.live_revision.id, 'from': instance.revisions.last().id, } def test_renders_with_all_the_diffs(self): submission = ApplicationSubmissionFactory() new_data = ApplicationSubmissionFactory(round=submission.round, form_fields=submission.form_fields).form_data submission.form_data = new_data submission.create_revision() response = self.get_page(submission) self.assertEqual(response.status_code, 200) class TestRevisionList(BaseSubmissionViewTestCase): base_view_name = 'revisions:list' user_factory = StaffFactory def get_kwargs(self, instance): return {'submission_pk': instance.pk} def test_list_doesnt_include_draft(self): submission = ApplicationSubmissionFactory() draft_revision = ApplicationRevisionFactory(submission=submission) submission.draft_revision = draft_revision submission.save() response = self.get_page(submission) self.assertNotIn(draft_revision, response.context['object_list']) def test_get_in_correct_order(self): submission = ApplicationSubmissionFactory() revision = ApplicationRevisionFactory(submission=submission) ApplicationRevision.objects.filter(id=revision.id).update(timestamp=datetime.now() - timedelta(days=1)) revision_older = ApplicationRevisionFactory(submission=submission) ApplicationRevision.objects.filter(id=revision_older.id).update(timestamp=datetime.now() - timedelta(days=2)) response = self.get_page(submission) self.assertSequenceEqual( response.context['object_list'], [submission.live_revision, revision, revision_older], ) class TestStaffSealedView(BaseSubmissionViewTestCase): user_factory = StaffFactory def test_redirected_to_sealed(self): submission = SealedSubmissionFactory() response = self.get_page(submission) url = self.url_from_pattern('funds:submissions:sealed', kwargs={'pk': submission.id}) self.assertRedirects(response, url) def test_cant_post_to_sealed(self): submission = SealedSubmissionFactory() response = self.post_page(submission, {'some': 'data'}, 'sealed') # Because of the redirect chain the url returned is not absolute url = self.url_from_pattern('funds:submissions:sealed', kwargs={'pk': submission.id}, absolute=False) self.assertRedirects(response, url) def test_non_sealed_unaffected(self): submission = ApplicationSubmissionFactory() response = self.get_page(submission) self.assertEqual(response.status_code, 200) def test_non_sealed_redirected_away(self): submission = ApplicationSubmissionFactory() response = self.get_page(submission, 'sealed') url = self.url_from_pattern('funds:submissions:detail', kwargs={'pk': submission.id}) self.assertRedirects(response, url) class TestSuperUserSealedView(BaseSubmissionViewTestCase): user_factory = SuperUserFactory def test_redirected_to_sealed(self): submission = SealedSubmissionFactory() response = self.get_page(submission) url = self.url_from_pattern('funds:submissions:sealed', kwargs={'pk': submission.id}) self.assertRedirects(response, url) def test_can_post_to_sealed(self): submission = SealedSubmissionFactory() response = self.post_page(submission, {}, 'sealed') url = self.url_from_pattern('funds:submissions:detail', kwargs={'pk': submission.id}) self.assertRedirects(response, url) def test_peeking_is_logged(self): submission = SealedSubmissionFactory() self.post_page(submission, {}, 'sealed') self.assertTrue('peeked' in self.client.session) self.assertTrue(str(submission.id) in self.client.session['peeked']) self.assertEqual(Activity.objects.count(), 1) self.assertTrue('sealed' in Activity.objects.first().message) def test_not_asked_again(self): submission = SealedSubmissionFactory() self.post_page(submission, {}, 'sealed') # Now request the page again response = self.get_page(submission) self.assertEqual(response.status_code, 200) def test_can_view_multiple_sealed(self): sealed_round = SealedRoundFactory() first, second = SealedSubmissionFactory.create_batch(2, round=sealed_round) self.post_page(first, {}, 'sealed') self.post_page(second, {}, 'sealed') self.assertTrue('peeked' in self.client.session) self.assertTrue(str(first.id) in self.client.session['peeked']) self.assertTrue(str(second.id) in self.client.session['peeked'])