2019-11-13 14:38:17 -08:00
|
|
|
import json
|
|
|
|
|
|
|
|
from grant.utils import totp_2fa
|
|
|
|
from grant.task.jobs import MilestoneDeadline
|
2019-11-05 11:38:34 -08:00
|
|
|
from datetime import datetime, timedelta
|
2019-01-29 14:21:06 -08:00
|
|
|
|
2019-11-05 11:38:34 -08:00
|
|
|
from grant.task.models import Task, db
|
|
|
|
from grant.task.jobs import PruneDraft
|
|
|
|
from grant.milestone.models import Milestone
|
2019-11-13 14:38:17 -08:00
|
|
|
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
|
2019-11-05 11:38:34 -08:00
|
|
|
|
|
|
|
from mock import patch, Mock
|
2019-01-29 14:21:06 -08:00
|
|
|
|
2019-11-13 14:38:17 -08:00
|
|
|
test_update = {
|
|
|
|
"title": "Update Title",
|
|
|
|
"content": "Update content."
|
|
|
|
}
|
2019-01-29 14:21:06 -08:00
|
|
|
|
2019-11-13 14:38:17 -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):
|
2019-11-13 14:38:17 -08:00
|
|
|
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, [])
|
|
|
|
|
2019-11-05 11:38:34 -08:00
|
|
|
@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)
|
2019-11-13 14:38:17 -08:00
|
|
|
|
|
|
|
@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):
|
2019-11-13 14:38:17 -08:00
|
|
|
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
|
2019-11-13 14:38:17 -08:00
|
|
|
|
|
|
|
# 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()
|