zcash-grant-system/backend/tests/task/test_api.py

343 lines
10 KiB
Python
Raw Normal View History

import json
from grant.utils import totp_2fa
from grant.task.jobs import MilestoneDeadline
from datetime import datetime, timedelta
2019-01-29 14:21:06 -08:00
from grant.task.models import Task, db
from grant.task.jobs import PruneDraft
from grant.milestone.models import Milestone
from grant.proposal.models import Proposal, ProposalUpdate
from grant.utils.enums import ProposalStatus, ProposalStage, Category
from ..config import BaseProposalCreatorConfig
from ..test_data import mock_blockchain_api_requests
from mock import patch, Mock
2019-01-29 14:21:06 -08:00
test_update = {
"title": "Update Title",
"content": "Update content."
}
2019-01-29 14:21:06 -08:00
milestones_data = [
{
"title": "All the money straightaway",
"content": "cool stuff with it",
"days_estimated": 30,
"payout_percent": "100",
"immediate_payout": False
}
]
2019-01-29 14:21:06 -08:00
class TestTaskAPI(BaseProposalCreatorConfig):
def p(self, path, data):
return self.app.post(path, data=json.dumps(data), content_type="application/json")
def login_admin(self):
# set admin
self.user.set_admin(True)
db.session.commit()
# login
r = self.p("/api/v1/admin/login", {
"username": self.user.email_address,
"password": self.user_password
})
self.assert200(r)
# 2fa on the natch
r = self.app.get("/api/v1/admin/2fa")
self.assert200(r)
# ... init
r = self.app.get("/api/v1/admin/2fa/init")
self.assert200(r)
codes = r.json['backupCodes']
secret = r.json['totpSecret']
uri = r.json['totpUri']
# ... enable/verify
r = self.p("/api/v1/admin/2fa/enable", {
"backupCodes": codes,
"totpSecret": secret,
"verifyCode": totp_2fa.current_totp(secret)
})
self.assert200(r)
return r
2019-01-29 14:21:06 -08:00
def test_proposal_reminder_task_is_created(self):
tasks = Task.query.filter(Task.execute_after <= datetime.now()).filter_by(completed=False).all()
self.assertEqual(tasks, [])
self.make_proposal_reminder_task()
2019-01-29 14:43:04 -08:00
tasks = Task.query.filter(Task.execute_after <= datetime.now()).filter_by(completed=False).all()
2019-01-29 14:21:06 -08:00
self.assertEqual(len(tasks), 1)
def test_proposal_reminder_task_is_marked_completed_after_call(self):
self.make_proposal_reminder_task()
tasks = Task.query.filter(Task.execute_after <= datetime.now()).filter_by(completed=False).all()
self.assertEqual(len(tasks), 1)
self.app.get("/api/v1/task")
tasks = Task.query.filter(Task.execute_after <= datetime.now()).filter_by(completed=False).all()
self.assertEqual(tasks, [])
@patch('grant.task.views.datetime')
def test_proposal_pruning(self, mock_datetime):
self.login_default_user()
resp = self.app.post(
"/api/v1/proposals/drafts",
)
proposal_id = resp.json['proposalId']
# make sure proposal was created
proposal = Proposal.query.get(proposal_id)
self.assertIsNotNone(proposal)
# make sure the task was created
self.assertStatus(resp, 201)
tasks = Task.query.all()
self.assertEqual(len(tasks), 1)
task = tasks[0]
self.assertEqual(resp.json['proposalId'], task.blob['proposal_id'])
self.assertFalse(task.completed)
# mock time so task will run when called
after_time = datetime.now() + timedelta(seconds=PruneDraft.PRUNE_TIME + 100)
mock_datetime.now = Mock(return_value=after_time)
# run task
resp = self.app.get("/api/v1/task")
self.assert200(resp)
# make sure task ran successfully
tasks = Task.query.all()
self.assertEqual(len(tasks), 1)
task = tasks[0]
self.assertTrue(task.completed)
proposal = Proposal.query.get(proposal_id)
self.assertIsNone(proposal)
def test_proposal_pruning_noops(self):
# ensure all proposal noop states work as expected
def status(p):
p.status = ProposalStatus.LIVE
def title(p):
p.title = 'title'
def brief(p):
p.brief = 'brief'
def content(p):
p.content = 'content'
def category(p):
p.category = Category.DEV_TOOL
def target(p):
p.target = '100'
def payout_address(p):
p.payout_address = 'address'
def milestones(p):
Milestone.make(milestones_data, p)
modifiers = [
status,
title,
brief,
content,
category,
target,
payout_address,
milestones
]
for modifier in modifiers:
proposal = Proposal.create(status=ProposalStatus.DRAFT)
proposal_id = proposal.id
modifier(proposal)
db.session.add(proposal)
db.session.commit()
blob = {
"proposal_id": proposal_id,
}
task = Task(
job_type=PruneDraft.JOB_TYPE,
blob=blob,
execute_after=datetime.now()
)
PruneDraft.process_task(task)
proposal = Proposal.query.get(proposal_id)
self.assertIsNotNone(proposal)
@patch('grant.task.jobs.send_email')
@patch('grant.task.views.datetime')
ZF Grants 2.1 (#496) * fix ccr pagination defaults * add ccr admin tests * add ccr user tests * checkpoint * fix tslint * request changes discussion flow mvp * admin - add discussion status * backend - add live drafts * admin - add live drafts * frontend - add live drafts * frontend - add edit discussion proposal * fix tsc * include DISCUSSION status in propsal listview * do not make live draft on admin request changes * hide live drafts from user proposal draft list * fix backend tests * add admin tests * add user tests * fix: liking, viewing discussion proposals, admin menu * admin - update hints for live drafts * fe - add better messaging when updating a proposal * be - fix like test * remove TODO comments * add new email types * fix storybook * add revision tab story * backend - implement proposal revisions * frontend - implement proposal revisions * update revision tab story * fix lint * remove set detection * email proposal followers on revision * restrict banner to team members only * misc bug fixes * update, add backend tests * add milestone title change to revision history story * fix milestones display in preview * allow archived proposals to be queried * implement archived proposal page * fix tsc * implement archived proposal get route * move styling into less * remove proposal archive parent id * handle archived proposal status * cleanup * remove contributions, switch to USD, implement quarters * use Qs to preserve formatting * handle edit only kyc * prevent ARCHIVED proposals from being sent to admin * display latest revision first * admin - proposal & ccr reject permanently * backend - proposal & ccr reject permanently * frontend - proposal & ccr reject permanently * fix tsc * use $ in milestone payout email * introduce custom filters to proposal listview * hide archive link on first revision * upgrade packages * add bech32 implementation * add z address validation with tests * fix tslint * use local address validation * fix tests, remove blockchain mock gets * add additional bad addresses * update briefs to include page break message * remove contributions routes, menu entry * disable countribution count admin stats * remove matching and pretty print in finance * fix tslint * separate out rejected permanently proposals * make removing proposals generic * allow linked tabs to be ignored * remove rejected permanently, bugfix * update preview link to point to rejected tab * implement rejected permanently tab, add tab message * refactor variable * fix tslint * fix tslint * send ccr reject permanently email on rejection * fix preview message * wire up proposal arbiter and rejected emails * disable tip jar in proposal and profile * sync ccr/proposal drafts on create form init * check invites on submit modal open * update team invite language * update team text when edit * fix ccr rejected permanently tag * text changes, email preview fix * display changes requested tag when in discussion with changes requested * enable social share on open for discussion proposals, update language * place sort below filter * derive filter from query string * use better filter names in query params * fix tslint * create snapshot of original proposal on first revision * clear invites between edits, account for additional changes not tracked in revisions * update tests * fix test * remove print * SameSite Fixes (#150) * QA Fixes 2 (#151) * set filters as query strings on change * remove rejected permanently tags * add dollar sign in financials legend * fix tsc * Copy Touchups (#152) * Email Fixes (#155) * fix ZEC in milestone payout emails * fix links in rejected permanently CCR/proposal emails * Poll for Team and Invite Changes in Create Flow (#153) * poll for team and invite changes in create flow * fix tslint Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * pretty print payouts by quarter (#156) Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * Remove Blockchain Module (#154) * remove blockchain route from backend, remove calls to node * revert blockchain_get removal * Add Tags to Proposal Cards (#157) * add tag to proposals and dynamically set v1 card height * listen on window resize * make card height props optional * set tag in bottom right, remove dynamic card resize, add dynamic tag resize * cleanup * cleanup Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * Improve Frontend Address Validation (#158) Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * Remove blockchain module (#162) * remove blockchain route from backend, remove calls to node * revert blockchain_get removal * Remove Blockchain App (#160) * remove blockchain app * remove blockchain app from travis Co-authored-by: Danny Skubak <skubakdj@gmail.com> * Proposal Edit Fixes (#161) * fe - display error if edit creation fails * be - restrict live draft publish Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * Restrict Arbiter Assignment (#159) Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * Email Copy updates * Remove Admin Financials Card * Hookup 'proposal_approved_without_funding' to admin email example * bump various package versions * Update yarn.lock files * Attach 'proposal_approved_without_funding' to backend example email * bump package versions Co-authored-by: Danny Skubak <skubakdj@gmail.com>
2020-04-07 19:56:32 -07:00
def test_milestone_deadline(self, mock_datetime, mock_send_email):
tasks = Task.query.filter_by(completed=False).all()
self.assertEqual(len(tasks), 0)
self.proposal.arbiter.user = self.user
db.session.add(self.proposal)
# unset immediate_payout so task will be added
for milestone in self.proposal.milestones:
if milestone.immediate_payout:
milestone.immediate_payout = False
db.session.add(milestone)
db.session.commit()
self.login_admin()
ZF Grants 2.1 (#496) * fix ccr pagination defaults * add ccr admin tests * add ccr user tests * checkpoint * fix tslint * request changes discussion flow mvp * admin - add discussion status * backend - add live drafts * admin - add live drafts * frontend - add live drafts * frontend - add edit discussion proposal * fix tsc * include DISCUSSION status in propsal listview * do not make live draft on admin request changes * hide live drafts from user proposal draft list * fix backend tests * add admin tests * add user tests * fix: liking, viewing discussion proposals, admin menu * admin - update hints for live drafts * fe - add better messaging when updating a proposal * be - fix like test * remove TODO comments * add new email types * fix storybook * add revision tab story * backend - implement proposal revisions * frontend - implement proposal revisions * update revision tab story * fix lint * remove set detection * email proposal followers on revision * restrict banner to team members only * misc bug fixes * update, add backend tests * add milestone title change to revision history story * fix milestones display in preview * allow archived proposals to be queried * implement archived proposal page * fix tsc * implement archived proposal get route * move styling into less * remove proposal archive parent id * handle archived proposal status * cleanup * remove contributions, switch to USD, implement quarters * use Qs to preserve formatting * handle edit only kyc * prevent ARCHIVED proposals from being sent to admin * display latest revision first * admin - proposal & ccr reject permanently * backend - proposal & ccr reject permanently * frontend - proposal & ccr reject permanently * fix tsc * use $ in milestone payout email * introduce custom filters to proposal listview * hide archive link on first revision * upgrade packages * add bech32 implementation * add z address validation with tests * fix tslint * use local address validation * fix tests, remove blockchain mock gets * add additional bad addresses * update briefs to include page break message * remove contributions routes, menu entry * disable countribution count admin stats * remove matching and pretty print in finance * fix tslint * separate out rejected permanently proposals * make removing proposals generic * allow linked tabs to be ignored * remove rejected permanently, bugfix * update preview link to point to rejected tab * implement rejected permanently tab, add tab message * refactor variable * fix tslint * fix tslint * send ccr reject permanently email on rejection * fix preview message * wire up proposal arbiter and rejected emails * disable tip jar in proposal and profile * sync ccr/proposal drafts on create form init * check invites on submit modal open * update team invite language * update team text when edit * fix ccr rejected permanently tag * text changes, email preview fix * display changes requested tag when in discussion with changes requested * enable social share on open for discussion proposals, update language * place sort below filter * derive filter from query string * use better filter names in query params * fix tslint * create snapshot of original proposal on first revision * clear invites between edits, account for additional changes not tracked in revisions * update tests * fix test * remove print * SameSite Fixes (#150) * QA Fixes 2 (#151) * set filters as query strings on change * remove rejected permanently tags * add dollar sign in financials legend * fix tsc * Copy Touchups (#152) * Email Fixes (#155) * fix ZEC in milestone payout emails * fix links in rejected permanently CCR/proposal emails * Poll for Team and Invite Changes in Create Flow (#153) * poll for team and invite changes in create flow * fix tslint Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * pretty print payouts by quarter (#156) Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * Remove Blockchain Module (#154) * remove blockchain route from backend, remove calls to node * revert blockchain_get removal * Add Tags to Proposal Cards (#157) * add tag to proposals and dynamically set v1 card height * listen on window resize * make card height props optional * set tag in bottom right, remove dynamic card resize, add dynamic tag resize * cleanup * cleanup Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * Improve Frontend Address Validation (#158) Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * Remove blockchain module (#162) * remove blockchain route from backend, remove calls to node * revert blockchain_get removal * Remove Blockchain App (#160) * remove blockchain app * remove blockchain app from travis Co-authored-by: Danny Skubak <skubakdj@gmail.com> * Proposal Edit Fixes (#161) * fe - display error if edit creation fails * be - restrict live draft publish Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * Restrict Arbiter Assignment (#159) Co-authored-by: Daniel Ternyak <dternyak@gmail.com> * Email Copy updates * Remove Admin Financials Card * Hookup 'proposal_approved_without_funding' to admin email example * bump various package versions * Update yarn.lock files * Attach 'proposal_approved_without_funding' to backend example email * bump package versions Co-authored-by: Danny Skubak <skubakdj@gmail.com>
2020-04-07 19:56:32 -07:00
# proposal needs to be DISCUSSION
self.proposal.status = ProposalStatus.DISCUSSION
# approve proposal with funding
resp = self.app.put(
"/api/v1/admin/proposals/{}/accept".format(self.proposal.id),
data=json.dumps({"isAccepted": True, "withFunding": True})
)
self.assert200(resp)
tasks = Task.query.filter_by(completed=False).all()
self.assertEqual(len(tasks), 1)
# fast forward the clock so task will run
after_time = datetime.now() + timedelta(days=365)
mock_datetime.now = Mock(return_value=after_time)
# run task
resp = self.app.get("/api/v1/task")
self.assert200(resp)
# make sure task ran
tasks = Task.query.filter_by(completed=False).all()
self.assertEqual(len(tasks), 0)
mock_send_email.assert_called()
@patch('grant.task.jobs.send_email')
def test_milestone_deadline_update_posted(self, mock_send_email):
tasks = Task.query.all()
self.assertEqual(len(tasks), 0)
# set date_estimated on milestone to be in the past
milestone = self.proposal.milestones[0]
milestone.date_estimated = datetime.now() - timedelta(hours=1)
db.session.add(milestone)
db.session.commit()
# make task
ms_deadline = MilestoneDeadline(self.proposal, milestone)
ms_deadline.make_task()
# check make task
tasks = Task.query.all()
self.assertEqual(len(tasks), 1)
# login and post proposal update
self.login_default_user()
resp = self.app.post(
"/api/v1/proposals/{}/updates".format(self.proposal.id),
data=json.dumps(test_update),
content_type='application/json'
)
self.assertStatus(resp, 201)
# run task
resp = self.app.get("/api/v1/task")
self.assert200(resp)
# make sure task ran and did NOT send out an email
tasks = Task.query.filter_by(completed=False).all()
self.assertEqual(len(tasks), 0)
mock_send_email.assert_not_called()
@patch('grant.task.jobs.send_email')
def test_milestone_deadline_noops(self, mock_send_email):
# make sure all milestone deadline noop states work as expected
def proposal_delete(p, m):
db.session.delete(p)
def proposal_status(p, m):
p.status = ProposalStatus.DELETED
db.session.add(p)
def proposal_stage(p, m):
p.stage = ProposalStage.CANCELED
db.session.add(p)
def milestone_delete(p, m):
db.session.delete(m)
def milestone_date_requested(p, m):
m.date_requested = datetime.now()
db.session.add(m)
def update_posted(p, m):
# login and post proposal update
self.login_default_user()
resp = self.app.post(
"/api/v1/proposals/{}/updates".format(proposal.id),
data=json.dumps(test_update),
content_type='application/json'
)
self.assertStatus(resp, 201)
modifiers = [
proposal_delete,
proposal_status,
proposal_stage,
milestone_delete,
milestone_date_requested,
update_posted
]
for modifier in modifiers:
# make proposal and milestone
proposal = Proposal.create(status=ProposalStatus.LIVE)
proposal.arbiter.user = self.other_user
proposal.team.append(self.user)
proposal_id = proposal.id
Milestone.make(milestones_data, proposal)
db.session.add(proposal)
db.session.commit()
# grab update count for blob
update_count = len(ProposalUpdate.query.filter_by(proposal_id=proposal_id).all())
# run modifications to trigger noop
proposal = Proposal.query.get(proposal_id)
milestone = proposal.milestones[0]
milestone_id = milestone.id
modifier(proposal, milestone)
db.session.commit()
# make task
blob = {
"proposal_id": proposal_id,
"milestone_id": milestone_id,
"update_count": update_count
}
task = Task(
job_type=MilestoneDeadline.JOB_TYPE,
blob=blob,
execute_after=datetime.now()
)
# run task
MilestoneDeadline.process_task(task)
# check to make sure noop occurred
mock_send_email.assert_not_called()