Zcash e2e basics (#329)

* e2e: remove more eth related stuff, update cypress

* BE: e2e endpoints and interceptions for emails and blockchain reqs

* e2e: expand browse test for proposals and comments

* e2e: remove old tests, add new ones

* e2e: intercept blockchain_get /contribution/addresses

* be: disable sentry for e2e, add DEBUG to e2e endpoint register conditional

* post-merge adjustments

* fix merge related bug

* post merge issue
This commit is contained in:
AMStrix 2019-03-13 18:36:06 -05:00 committed by Daniel Ternyak
parent a043c2dfa6
commit e018f11018
26 changed files with 2250 additions and 3710 deletions

View File

@ -10,10 +10,9 @@ from flask_security import SQLAlchemyUserDatastore
from flask_sslify import SSLify
from sentry_sdk.integrations.flask import FlaskIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
from grant import commands, proposal, user, comment, milestone, admin, email, blockchain, task, rfp
from grant import commands, proposal, user, comment, milestone, admin, email, blockchain, task, rfp, e2e
from grant.extensions import bcrypt, migrate, db, ma, security, limiter
from grant.settings import SENTRY_RELEASE, ENV
from grant.settings import SENTRY_RELEASE, ENV, E2E_TESTING, DEBUG
from grant.utils.auth import AuthException, handle_auth_error, get_authed_user
from grant.utils.exceptions import ValidationException
@ -52,19 +51,16 @@ def create_app(config_objects=["grant.settings"]):
else:
return jsonify({"message": error_message}), err.code
@app.errorhandler(404)
def handle_notfound_error(err):
error_message = "Unknown route '{} {}'".format(request.method, request.path)
return jsonify({"message": error_message}), 404
@app.errorhandler(429)
def handle_limit_error(err):
app.logger.warn(f'Rate limited request to {request.method} {request.path} from ip {request.remote_addr}')
return jsonify({"message": "Youve done that too many times, please wait and try again later"}), 429
@app.errorhandler(Exception)
def handle_exception(err):
sentry_sdk.capture_exception(err)
@ -80,7 +76,7 @@ def create_app(config_objects=["grant.settings"]):
register_shellcontext(app)
register_commands(app)
if not app.config.get("TESTING"):
if not (app.config.get("TESTING") or E2E_TESTING):
sentry_logging = LoggingIntegration(
level=logging.INFO,
event_level=logging.ERROR
@ -130,6 +126,9 @@ def register_blueprints(app):
app.register_blueprint(blockchain.views.blueprint)
app.register_blueprint(task.views.blueprint)
app.register_blueprint(rfp.views.blueprint)
if E2E_TESTING and DEBUG:
print('Warning: e2e end-points are open, this should only be the case for development or testing')
app.register_blueprint(e2e.views.blueprint)
def register_shellcontext(app):

View File

@ -0,0 +1 @@
from . import views

150
backend/grant/e2e/views.py Normal file
View File

@ -0,0 +1,150 @@
from datetime import datetime
from random import randint
from math import floor
from flask import Blueprint
from grant.comment.models import Comment
from grant.extensions import db
from grant.milestone.models import Milestone
from grant.proposal.models import (
Proposal,
ProposalArbiter,
ProposalContribution,
)
from grant.user.models import User, admin_user_schema
from grant.utils.enums import (
ProposalStatus,
ProposalStage,
Category,
ContributionStatus,
)
last_email = None
def create_proposals(count, category, stage, with_comments=False):
user = User.query.filter_by().first()
for i in range(count):
p = Proposal.create(
stage=stage,
status=ProposalStatus.LIVE,
title=f'Fake Proposal #{i} {category} {stage}',
content=f'My fake proposal content, numero {i}',
brief=f'This is proposal {i} generated by e2e testing',
category=category,
target="123.456",
payout_address="fake123",
deadline_duration=100
)
p.date_published = datetime.now()
p.team.append(user)
db.session.add(p)
db.session.flush()
num_ms = randint(1, 9)
for j in range(num_ms):
m = Milestone(
title=f'Fake MS {j}',
content=f'Fake milestone #{j} on fake proposal #{i} {category} {stage}!',
date_estimated=datetime.now(),
payout_percent=str(floor(1 / num_ms * 100)),
immediate_payout=j == 0,
proposal_id=p.id,
index=j
)
db.session.add(m)
# limit comment creation as it is slow
if i == 0 and with_comments:
for j in range(31):
c = Comment(
proposal_id=p.id,
user_id=user.id,
parent_comment_id=None,
content=f'Fake comment #{j} on fake proposal #{i} {category} {stage}!'
)
db.session.add(c)
if stage == ProposalStage.FUNDING_REQUIRED:
stake = p.create_contribution('1', None, True)
stake.confirm('fakestaketxid', '1')
db.session.add(stake)
db.session.flush()
fund = p.create_contribution('100', None, False)
fund.confirm('fakefundtxid0', '100')
db.session.add(fund)
db.session.flush()
p.status = ProposalStatus.LIVE
db.session.add(p)
db.session.flush()
# db.session.flush()
def create_user(key: str):
pw = f"e2epassword{key}"
user = User.create(
email_address=f"{key}@testing.e2e",
password=pw,
display_name=f"{key} Endtoenderson",
title=f"title{key}",
)
user.email_verification.has_verified = True
db.session.add(user)
db.session.flush()
dump = admin_user_schema.dump(user)
dump['password'] = pw
return dump
blueprint = Blueprint('e2e', __name__, url_prefix='/api/v1/e2e')
@blueprint.route("/setup", methods=["GET"])
def setup():
db.session.commit() # important, otherwise drop_all hangs
db.drop_all()
db.session.commit()
db.create_all()
db.session.commit()
default_user = create_user('default')
other_user = create_user('other')
create_proposals(12, Category.COMMUNITY, ProposalStage.FUNDING_REQUIRED, True)
create_proposals(13, Category.CORE_DEV, ProposalStage.WIP, False)
create_proposals(15, Category.DOCUMENTATION, ProposalStage.COMPLETED)
create_proposals(5, Category.ACCESSIBILITY, ProposalStage.FAILED)
db.session.commit()
return {
'default_user': default_user,
'other_user': other_user,
'proposalCounts': {
'categories': [
{"key": Category.COMMUNITY, "count": 12},
{"key": Category.CORE_DEV, "count": 13},
{"key": Category.DOCUMENTATION, "count": 15},
{"key": Category.ACCESSIBILITY, "count": 5},
],
'stages': [
{"key": ProposalStage.FUNDING_REQUIRED, "count": 12},
{"key": ProposalStage.WIP, "count": 13},
{"key": ProposalStage.COMPLETED, "count": 15},
{"key": ProposalStage.FAILED, "count": 5},
]
}
}
@blueprint.route("/email", methods=["GET"])
def get_email():
return last_email
@blueprint.route("/contribution/confirm", methods=["GET"])
def confirm_contributions():
contributions = ProposalContribution.query \
.filter(ProposalContribution.status == ContributionStatus.PENDING).all()
for c in contributions:
c.confirm('fakefundedtxid1', '23.456')
db.session.add(c)
c.proposal.set_funded_when_ready()
db.session.commit()
return {}

View File

@ -1,13 +1,14 @@
from .subscription_settings import EmailSubscription, is_subscribed
from sendgrid.helpers.mail import Email, Mail, Content
from python_http_client import HTTPError
from grant.utils.misc import make_url
from sentry_sdk import capture_exception
from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, SENDGRID_DEFAULT_FROMNAME, UI
from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, UI, E2E_TESTING
import sendgrid
from threading import Thread
from flask import render_template, Markup, current_app
from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, SENDGRID_DEFAULT_FROMNAME, UI
from sentry_sdk import capture_exception
from grant.utils.misc import make_url
from python_http_client import HTTPError
from sendgrid.helpers.mail import Email, Mail, Content
from .subscription_settings import EmailSubscription, is_subscribed
default_template_args = {
'home_url': make_url('/'),
@ -389,8 +390,14 @@ def sendgrid_send(mail):
type = mail.___type
try:
sg = sendgrid.SendGridAPIClient(apikey=SENDGRID_API_KEY)
res = sg.client.mail.send.post(request_body=mail.get())
current_app.logger.info('Just sent an email to %s of type %s, response code: %s' % (to, type, res.status_code))
if E2E_TESTING:
from grant.e2e import views
views.last_email = mail.get()
current_app.logger.info(f'Just set last_email for e2e to pickup, to: {to}, type: {type}')
else:
res = sg.client.mail.send.post(request_body=mail.get())
current_app.logger.info('Just sent an email to %s of type %s, response code: %s' %
(to, type, res.status_code))
except HTTPError as e:
current_app.logger.info('An HTTP error occured while sending an email to %s - %s: %s' %
(to, e.__class__.__name__, e))

View File

@ -15,7 +15,9 @@ env.read_env()
ENV = env.str("FLASK_ENV", default="production")
DEBUG = ENV == "development"
SITE_URL = env.str('SITE_URL', default='https://zfnd.org')
SQLALCHEMY_DATABASE_URI = env.str("DATABASE_URL")
E2E_TESTING = env.str("E2E_TESTING", default=None)
E2E_DATABASE_URL = env.str("E2E_DATABASE_URL", default=None)
SQLALCHEMY_DATABASE_URI = E2E_DATABASE_URL if E2E_TESTING else env.str("DATABASE_URL")
SQLALCHEMY_ECHO = False # True will print queries to log
QUEUES = ["default"]
SECRET_KEY = env.str("SECRET_KEY")

View File

@ -1,5 +1,5 @@
import requests
from grant.settings import BLOCKCHAIN_REST_API_URL, BLOCKCHAIN_API_SECRET
from grant.settings import BLOCKCHAIN_REST_API_URL, BLOCKCHAIN_API_SECRET, E2E_TESTING
### REST API ###
@ -12,6 +12,8 @@ def handle_res(res):
def blockchain_get(path, params=None):
if E2E_TESTING:
return blockchain_rest_e2e(path, params)
res = requests.get(
f'{BLOCKCHAIN_REST_API_URL}{path}',
headers={'authorization': BLOCKCHAIN_API_SECRET},
@ -21,9 +23,25 @@ def blockchain_get(path, params=None):
def blockchain_post(path, data=None):
if E2E_TESTING:
return blockchain_rest_e2e(path, data)
res = requests.post(
f'{BLOCKCHAIN_REST_API_URL}{path}',
headers={'authorization': BLOCKCHAIN_API_SECRET},
json=data,
)
return handle_res(res)
def blockchain_rest_e2e(path, data):
if '/bootstrap' in path:
return {
'startHeight': 123,
'currentHeight': 456,
}
if '/contribution/addresses' in path:
return {
'transparent': 't123',
}
raise Exception(f'blockchain_post_e2e does not recognize path: {path}')

View File

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,46 @@
/// <reference types="cypress"/>
describe("auth.recover", () => {
let stubs: any;
before(() => {
cy.request("http://localhost:5000/api/v1/e2e/setup").then(
r => (stubs = r.body)
);
});
it("should be able to recover account", () => {
cy.visit("/auth/sign-in");
cy.title().should("include", "ZF Grants - Sign in");
cy.get("html").then(el => (el[0].style.scrollBehavior = "auto"));
cy.contains("a", "Recover your acc").click();
cy.get("input[placeholder='email address']").type(
stubs.defaultUser.emailAddress
);
cy.contains("button", "Send").click();
cy.contains("Please check your email ");
cy.request("http://localhost:5000/api/v1/e2e/email").then(r => {
const txt = r.body.content.find((x: any) => x.type === "text/plain")
.value;
const url = txt.match(/http.*\/email\/recover.*/)[0];
cy.visit(url);
});
cy.contains("Reset");
cy.get("input[name='password']").type("muhnewpassword");
cy.get("input[name='passwordConfirm']").type("muhnewpassword");
cy.contains("Button", "Change pass").click();
cy.contains("Password has been reset");
cy.contains("button", "Sign in").click();
cy.get("input[placeholder='email']")
.clear()
.type(stubs.defaultUser.emailAddress);
cy.get("input[placeholder='password']")
.clear()
.type("muhnewpassword");
cy.contains("button", "Sign in").click();
cy.contains("default Endtoenderson");
cy.contains("button", "Edit profile");
});
});

View File

@ -0,0 +1,51 @@
/// <reference types="cypress"/>
describe("auth.sign-in", () => {
let stubs: any;
before(() => {
cy.request("http://localhost:5000/api/v1/e2e/setup").then(
r => (stubs = r.body)
);
});
it("should be able to login", () => {
cy.visit("/");
cy.title().should("include", "ZF Grants - Home");
cy.get("html").then(el => (el[0].style.scrollBehavior = "auto"));
cy.contains("a", "Sign in").then(x => console.log(stubs));
cy.contains("a", "Sign in").click();
cy.get("input[placeholder='email']").type(stubs.defaultUser.emailAddress);
cy.get("input[placeholder='password']").type(
stubs.defaultUser.password + "bad"
);
cy.contains("button", "Sign in").click();
cy.contains("Invalid passw");
cy.get("input[placeholder='email']")
.clear()
.type("notin@thesystem.com");
cy.get("input[placeholder='password']")
.clear()
.type(stubs.defaultUser.password);
cy.contains("button", "Sign in").click();
cy.contains("No user exists");
cy.get("input[placeholder='email']")
.clear()
.type(stubs.defaultUser.emailAddress);
cy.get("input[placeholder='password']")
.clear()
.type(stubs.defaultUser.password);
cy.contains("button", "Sign in").click();
cy.contains("default Endtoenderson");
cy.contains("button", "Edit profile");
cy.get(".AuthButton").click();
cy.contains("a", "Settings").click();
cy.contains("Account");
cy.contains("Notifications");
cy.contains("Change Password");
});
});

View File

@ -1,14 +0,0 @@
/// <reference types="cypress"/>
import {
loadWeb3,
increaseTime,
syncTimeWithEvm,
randomString
} from "../helpers";
import { createDemoProposal, authenticateUser } from "../parts";
describe("authenticate", () => {
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
});

View File

@ -1,47 +1,66 @@
/// <reference types="cypress"/>
import { loadWeb3, randomString, randomHex } from "../helpers";
describe("browse", () => {
let stubs;
before(() => {
cy.request("http://localhost:5000/api/v1/e2e/setup").then(
r => (stubs = r.body)
);
});
it("should load and be able to browse pages", () => {
cy.visit("http://localhost:3000", { onBeforeLoad: loadWeb3(0) });
cy.title().should("include", "Grant.io - Home");
// cy.visit("http://localhost:3000");
cy.visit("/");
cy.title().should("include", "ZF Grants - Home");
cy.get("html").then(el => (el[0].style.scrollBehavior = "auto"));
// test hero create link
cy.get('.Home-hero-buttons a[href="/create"]')
// {force: true} here overcomes a strange issue where the button moves up under the header
// this is likely a cypress scroll related problem
.click({ force: true });
cy.title().should("include", "Grant.io - Create a Proposal");
cy.contains("a", "Proposals").click();
// browse back home
cy.get('.Header a[href="/"]').click();
cy.contains(".ant-select", "Newest").click();
cy.wait(100);
cy.contains(".ant-select-dropdown", "Oldest").click();
// test hero explore link
cy.get('.Home-hero-buttons a[href="/proposals"]')
// {force: true} here overcomes a strange issue where the button moves up under the header
// this is likely a cypress-related problem
.click({ force: true });
cy.title().should("include", "Grant.io - Browse proposals");
cy.contains(
".ProposalCard",
"Fake Proposal #0 COMMUNITY FUNDING_REQ"
).click();
cy.contains("h1", "Fake Proposal #0 COMMUNITY FUNDING_REQ");
cy.contains(".ant-tabs-tab", "Discussion").click();
cy.contains("Fake comment #30");
cy.contains("Fake comment #21");
cy.get(".ProposalComments").should("not.contain", "Fake comment #20");
cy.contains("button", "Older Comments").click();
cy.contains("Fake comment #11");
cy.get(".ProposalComments").should("not.contain", "Fake comment #10");
// browse back home
cy.get('.Header a[href="/"]').click();
cy.contains("a", "Proposals").click();
cy.contains(".ant-radio-wrapper", "All").click(); // FILTER
cy.contains(".ProposalCard", "Fake Proposal #0 COMMUNITY FUNDING_REQUIRED");
cy.contains(".ant-pagination-item", "5");
cy.contains(".ant-radio-wrapper", "In prog").click(); // FILTER
cy.contains(".ProposalCard", "Fake Proposal #0 CORE_DEV WIP");
cy.contains(".ant-pagination-item", "2").click();
cy.contains(".ProposalCard", "Fake Proposal #12 CORE_DEV WIP");
// browse to create via header link
cy.get('.Header a[href="/create"]').click();
cy.title().should("include", "Grant.io - Create a Proposal");
cy.contains("a", "Requests").click();
// browse to explore via header link
cy.get('.Header a[href="/proposals"]').click();
cy.title().should("include", "Grant.io - Browse proposals");
cy.contains("a", "Start a Proposal").click();
cy.title().should("include", "ZF Grants - Sign in");
cy.contains("Authorization required");
// browse footer links
cy.get('.Footer a[href="/about"]').click();
cy.title().should("include", "Grant.io - About");
cy.get('.Footer a[href="/contact"]').click();
cy.title().should("include", "Grant.io - Contact");
cy.get('.Footer a[href="/tos"]').click();
cy.title().should("include", "Grant.io - Terms of Service");
cy.get('.Footer a[href="/privacy"]').click();
cy.title().should("include", "Grant.io - Privacy Policy");
// cy.contains("a", "About").click(); // external site
cy.contains("a", "Contact").click({ force: true });
cy.contains("h1", "Contact");
cy.contains("a", "Terms of").click({ force: true });
cy.contains("h1", "Terms");
cy.contains("a", "Privacy").click({ force: true });
cy.contains("h1", "Privacy");
cy.contains("a", "Code of").click({ force: true });
cy.contains("h1", "Community");
cy.contains("a", "Sign in").click();
cy.contains("a", "Recover your").click();
cy.contains("h1", "Account Recovery");
cy.contains("a", "Sign in").click();
cy.contains("a", "Create a").click();
});
});

View File

@ -1,47 +0,0 @@
/// <reference types="cypress"/>
import {
loadWeb3,
increaseTime,
syncTimeWithEvm,
randomString
} from "../helpers";
import { createDemoProposal, authenticateUser } from "../parts";
describe("proposal", () => {
const id = randomString();
const title = `[${id}] e2e create cancel`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
(Cypress as any).runner.stop();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("cancels the proposal", () => {
cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click();
cy.contains(".ant-dropdown-menu-item", "Cancel proposal").click();
cy.contains(".ant-modal-footer > div button", "Confirm").click();
cy.contains("body", "Proposal didnt get funded", { timeout: 20000 });
cy.get(".ant-modal-wrap").should("not.be.visible");
cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click();
cy.contains(".ant-dropdown-menu-item", "Cancel proposal").should(
"have.attr",
"aria-disabled",
"true"
);
});
it("should appear unfunded to outsiders (account 9)", () => {
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(9) }));
cy.contains("body", "Proposal didnt get funded", { timeout: 20000 });
});
});

View File

@ -1,173 +0,0 @@
/// <reference types="cypress"/>
import { loadWeb3, randomString, randomHex, syncTimeWithEvm } from "../helpers";
import { authenticateUser } from "../parts";
describe("create.flow", () => {
const time = new Date().toLocaleString();
const id = randomString();
const randomEthHex = randomHex(32);
const nextYear = new Date().getUTCFullYear() + 1;
const proposal = {
title: `[${id}] e2e create flow`,
brief: "e2e brief",
category: "Community", // .anticon-team
targetAmount: 5,
body: `#### e2e Proposal ${id} {enter} **created** ${time} `,
team: [
{
name: "Alisha Endtoend",
title: "QA Robot0",
ethAddress: `0x0000${randomEthHex}0000`,
emailAddress: `qa.alisha.${id}@grant.io`
},
{
name: "Billy Endtoend",
title: "QA Robot1",
ethAddress: `0x1111${randomEthHex}1111`,
emailAddress: `qa.billy.${id}@grant.io`
}
],
milestones: [
{
title: `e2e Milestone ${id} 0`,
body: `e2e Milestone ${id} {enter} body 0`,
date: {
y: nextYear,
m: "Jan",
expect: "January " + nextYear
}
},
{
title: `e2e Milestone ${id} 1`,
body: `e2e Milestone ${id} {enter} body 1`,
date: {
y: nextYear,
m: "Feb",
expect: "February " + nextYear
}
}
]
};
afterEach(function() {
if (this.currentTest.state === "failed") {
(Cypress as any).runner.stop();
}
});
context("create flow wizard", () => {
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("create flow step 1", () => {
cy.get('[href="/create"]').click();
syncTimeWithEvm(cy);
cy.get('.CreateFlow input[name="title"]').type(proposal.title);
cy.get('.CreateFlow textarea[name="brief"]').type(proposal.brief);
cy.contains("Select a category").click();
cy.get(".ant-select-dropdown li .anticon-team").click();
cy.get('.CreateFlow input[name="amountToRaise"]').type(
"" + proposal.targetAmount
);
cy.wait(1000);
cy.contains(".CreateFlow-footer-button", "Continue").click();
});
it("create flow step 2", () => {
cy.get("button.TeamForm-add").click();
cy.get('.TeamMember-info input[name="name"]').type(proposal.team[1].name);
cy.get('.TeamMember-info input[name="title"]').type(
proposal.team[1].title
);
cy.get('.TeamMember-info input[name="ethAddress"]').type(
proposal.team[1].ethAddress
);
cy.get('.TeamMember-info input[name="emailAddress"]').type(
proposal.team[1].emailAddress
);
cy.get("button")
.contains("Save changes")
.click({ force: true });
cy.wait(1000);
cy.contains(".CreateFlow-footer-button", "Continue").click();
});
it("create flow step 3", () => {
cy.get(".DraftEditor-editorContainer > div").type(proposal.body);
cy.get(".mde-tabs > :nth-child(2)").click();
cy.wait(1000);
cy.contains(".CreateFlow-footer-button", "Continue").click();
});
it("create flow step 4", () => {
cy.get('input[name="title"]').type(proposal.milestones[0].title);
cy.get('textarea[name="body"]').type(proposal.milestones[0].body);
cy.get('input[placeholder="Expected completion date"]').click();
cy.get(".ant-calendar-month-panel-next-year-btn").click();
cy.get(".ant-calendar-month-panel-month")
.contains(proposal.milestones[0].date.m)
.click();
cy.get(".ant-calendar-picker-input").should(
"have.value",
proposal.milestones[0].date.expect
);
cy.get("button")
.contains("Add another milestone")
.click({ force: true });
cy.get('input[name="title"]')
.eq(1)
.type(proposal.milestones[1].title);
cy.get('textarea[name="body"]')
.eq(1)
.type(proposal.milestones[1].body);
cy.get('input[placeholder="Expected completion date"]')
.eq(1)
.click();
cy.get(".ant-calendar-month-panel-next-year-btn").click();
cy.get(".ant-calendar-month-panel-month")
.contains(proposal.milestones[1].date.m)
.click();
cy.get(".ant-calendar-picker-input")
.eq(1)
.should("have.value", proposal.milestones[1].date.expect);
cy.wait(1000);
cy.contains(".CreateFlow-footer-button", "Continue").click();
});
it("create flow step 5", () => {
cy.window()
.then(w => (w as any).web3.eth.getAccounts())
.then(accts => {
cy.get('input[name="payOutAddress"]').type(accts[0]);
cy.get("button")
.contains("Add another trustee")
.click({ force: true });
cy.get(
'input[placeholder="0x8B0B72F8bDE212991135668922fD5acE557DE6aB"]'
)
.eq(1)
.type(accts[1]);
cy.get('input[name="deadline"][value="2592000"]').click({
force: true
});
cy.get('input[name="milestoneDeadline"][value="259200"]').click({
force: true
});
});
cy.wait(1000);
cy.contains(".CreateFlow-footer-button", "Continue").click();
});
it("publishes the proposal", () => {
cy.get("button")
.contains("Publish")
.click();
cy.get(".CreateFinal-loader-text").contains("Deploying contract...");
cy.get(".CreateFinal-message-text a", { timeout: 20000 })
.contains("Click here")
.click();
cy.get(".Proposal-top-main-title").contains(proposal.title);
});
});
});

View File

@ -1,61 +0,0 @@
/// <reference types="cypress"/>
import {
loadWeb3,
increaseTime,
syncTimeWithEvm,
randomString
} from "../helpers";
import { createDemoProposal, authenticateUser } from "../parts";
describe("create.fund.cancel", () => {
const id = randomString();
const title = `[${id}] e2e create fund cancel`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
(Cypress as any).runner.stop();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("create demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("funds the proposal with account 5", () => {
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.get(".ant-input", { timeout: 20000 }).type(amount);
cy.get(".ant-form > .ant-btn").click();
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("cancels the proposal (refund contributors)", () => {
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click();
cy.contains(".ant-dropdown-menu-item", "Refund contributors").click();
cy.contains(".ant-modal-footer > div button", "Confirm").click();
cy.get(".ant-modal-wrap", { timeout: 20000 }).should("not.be.visible");
cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click();
cy.contains(".ant-dropdown-menu-item", "Refund contributors").should(
"have.attr",
"aria-disabled",
"true"
);
});
it("refunds the contributor (account 5)", () => {
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-tabs-nav > :nth-child(1) > :nth-child(4)", "Refund", {
timeout: 20000
}).click();
// force disables cypress' auto scrolling which messes up UI in this case
cy.contains(".ant-btn", "Get your refund").click({ force: true });
cy.contains("body", "Your refund has been processed", { timeout: 20000 });
});
});

View File

@ -1,126 +0,0 @@
/// <reference types="cypress"/>
import {
loadWeb3,
randomString,
syncTimeWithEvm,
increaseTime
} from "../helpers";
import { createDemoProposal, fundProposal, authenticateUser } from "../parts";
describe("create.fund.complete.minority-no-votes", () => {
const id = randomString();
const title = `[${id}] e2e minority no-votes complete`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
//(Cypress as any).runner.stop();
this.skip();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("funds the proposal from accounts 5, 6 & 7", () => {
fundProposal(cy, 5, 0.1);
fundProposal(cy, 6, 0.2);
fundProposal(cy, 7, 0.7);
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout", () => {
// MILESTONE 1
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
});
it("requests milestone 2 payout", () => {
// MILESTONE 2
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
it("minority funder (acct 5) votes no", () => {
// VOTE NO
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 })
.click()
.should("have.class", "ant-btn-loading");
cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 });
});
it("expires milestone 2 voting period & receives payout", () => {
// EXPIRE
increaseTime(cy, 70000);
// RECEIVE PAYOUT
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).click();
});
it("requests milestone 3 payout", () => {
// MILESTONE 3
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
it("minority funder (acct 5) votes no", () => {
// VOTE NO
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 })
.click()
.should("have.class", "ant-btn-loading");
cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 });
});
it("expires milestone 3 voting period & receives payout", () => {
// EXPIRE
increaseTime(cy, 70000);
// RECEIVE PAYOUT
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).click();
});
it("should not have receive button", () => {
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).should("not.exist");
});
});

View File

@ -1,103 +0,0 @@
/// <reference types="cypress"/>
import {
loadWeb3,
increaseTime,
syncTimeWithEvm,
randomString
} from "../helpers";
import { createDemoProposal, authenticateUser } from "../parts";
describe("create.fund.complete", () => {
const id = randomString();
const title = `[${id}] e2e create fund complete`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
//(Cypress as any).runner.stop();
this.skip();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("create demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("funds the proposal with account 5", () => {
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.get(".ant-input", { timeout: 20000 }).type(amount);
cy.get(".ant-form > .ant-btn").click();
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout", () => {
// MILESTONE 1
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request initial payout",
{ timeout: 20000 }
)
.as("RequestPayout")
.click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
});
it("requests and receives milestone 2 payout", () => {
// MILESTONE 2
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
// EXPIRE
increaseTime(cy, 70000);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).click();
});
it("requests and receives milestone 3 payout", () => {
// MILESTONE 3
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
// EXPIRE
increaseTime(cy, 70000);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).click();
});
it("should not have receive button", () => {
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout",
{ timeout: 20000 }
).should("not.exist");
});
});

View File

@ -1,60 +0,0 @@
/// <reference types="cypress"/>
import { loadWeb3, randomString, syncTimeWithEvm } from "../helpers";
import { createDemoProposal, fundProposal, authenticateUser } from "../parts";
describe("create.fund.ms2.majority-no-vote", () => {
const id = randomString();
const title = `[${id}] e2e ms2 majority no-vote`;
const amount = "1";
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("fund the proposal with 5th account", () => {
fundProposal(cy, 5, 1);
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout for milestone 1", () => {
// MILESTONE 1
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
);
});
it("requests milestone 2 payout", () => {
// MILESTONE 2
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
it("vote against milestone 2 payout as account 5", () => {
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 }).click();
cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 });
});
});

View File

@ -1,86 +0,0 @@
/// <reference types="cypress"/>
import {
loadWeb3,
randomString,
syncTimeWithEvm,
increaseTime
} from "../helpers";
import { createDemoProposal, fundProposal, authenticateUser } from "../parts";
describe("create.fund.ms2.no-vote.re-vote", () => {
const id = randomString();
const title = `[${id}] e2e ms2 no-vote expire re-vote`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
//(Cypress as any).runner.stop();
this.skip();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("fund the proposal with 5th account", () => {
fundProposal(cy, 5, 1);
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout for milestone 1", () => {
// MILESTONE 1
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
});
it("request milestone 2 payout", () => {
// MILESTONE 2
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
it("vote against milestone 2 payout as 5th account", () => {
// reload page with 5th account
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 })
.click()
.should("have.class", "ant-btn-loading");
cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 });
});
it("milestone 2 vote expires and payout is requested again", () => {
// EXPIRE
increaseTime(cy, 70000);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains("Payout was voted against");
// RE-REQUEST PAYOUT
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
});

View File

@ -1,69 +0,0 @@
/// <reference types="cypress"/>
import {
loadWeb3,
randomString,
syncTimeWithEvm,
increaseTime
} from "../helpers";
import { createDemoProposal, fundProposal, authenticateUser } from "../parts";
describe("create.fund.ms2.refund-after-payout", () => {
const id = randomString();
const title = `[${id}] e2e ms2 refund after payout`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
//(Cypress as any).runner.stop();
this.skip();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("fund the proposal with account 5", () => {
fundProposal(cy, 5, 1);
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout for milestone 1", () => {
// MILESTONE 1
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
);
});
it("majority refund vote and get refund (account 5)", () => {
// REFUND
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-tabs-nav > :nth-child(1) > :nth-child(4)", "Refund", {
timeout: 20000
}).click();
// INCREASE TIME
increaseTime(cy, 70000);
// force disables cypress' auto scrolling which messes up UI in this case
cy.contains(".ant-btn", "Vote for refund").click({ force: true });
cy.contains(".ant-btn", "Get your refund", { timeout: 20000 }).click({
force: true
});
cy.contains("body", "Your refund has been processed", { timeout: 20000 });
});
});

View File

@ -1,93 +0,0 @@
/// <reference types="cypress"/>
import {
loadWeb3,
randomString,
syncTimeWithEvm,
increaseTime
} from "../helpers";
import { createDemoProposal, fundProposal, authenticateUser } from "../parts";
describe("create.fund.ms2.revert-no-vote", () => {
const id = randomString();
const title = `[${id}] e2e ms2 revert no-vote`;
const amount = "1";
afterEach(function() {
if (this.currentTest.state === "failed") {
//(Cypress as any).runner.stop();
this.skip();
}
});
it("authenticates and creates if necessary", () => {
authenticateUser(cy, 0);
});
it("creates demo proposal", () => {
createDemoProposal(cy, title, amount);
});
it("funds the proposal with 5th account", () => {
fundProposal(cy, 5, 1);
cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains(
"Proposal has been funded"
);
});
it("receives initial payout for milestone 1", () => {
// MILESTONE 1
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click();
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive initial payout",
{ timeout: 20000 }
).click();
});
it("requests milestone 2 payout", () => {
// MILESTONE 2
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
).click();
cy.contains(".MilestoneAction-progress-text", "voted against payout", {
timeout: 20000
});
});
it("votes against milestone 2 payout as account 5 and then reverts the vote", () => {
// NO VOTE... REVERT
syncTimeWithEvm(cy);
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) }));
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 })
.click()
.should("have.class", "ant-btn-loading");
cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 })
.click()
.should("have.class", "ant-btn-loading");
cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 });
});
it("milestone 2 vote expires and payout is received", () => {
// EXPIRE
increaseTime(cy, 70000);
// PAYOUT
cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) }));
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Receive milestone payout"
).click();
});
it("milestone 3 becomes active", () => {
// MILESTONE 3
cy.contains(
".MilestoneAction-top > div > .ant-btn",
"Request milestone payout",
{ timeout: 20000 }
);
});
});

View File

@ -0,0 +1,77 @@
/// <reference types="cypress"/>
describe("proposal.fund", () => {
let stubs: any;
before(() => {
cy.request("http://localhost:5000/api/v1/e2e/setup").then(
r => (stubs = r.body)
);
});
it("should be able to make anonymous contributions", () => {
cy.visit("/proposals");
cy.title().should("include", "ZF Grants - Browse proposals");
cy.get("html").then(el => (el[0].style.scrollBehavior = "auto"));
cy.contains(".ant-radio-wrapper", "Funding req").click();
cy.contains(
".ProposalCard",
"Fake Proposal #11 COMMUNITY FUNDING_REQUIRED"
).click();
cy.get("input[name='amountToRaise']").type("100");
cy.contains("Cannot exceed maximum");
cy.get("input[name='amountToRaise']")
.clear()
.type("23.456");
cy.contains("button", "Fund this").click();
cy.contains("anonymous");
cy.contains("button", "I accept").click();
cy.contains("button", "Ive sent it").click();
cy.contains("button", "Done").click();
});
it("should be able to make contributions", () => {
cy.visit("/auth/sign-in");
cy.get("html").then(el => (el[0].style.scrollBehavior = "auto"));
// login
cy.get("input[placeholder='email']")
.clear()
.type(stubs.defaultUser.emailAddress);
cy.get("input[placeholder='password']")
.clear()
.type(stubs.defaultUser.password);
cy.contains("button", "Sign in").click();
cy.contains("default Endtoenderson");
cy.contains("a", "Proposals").click();
cy.title().should("include", "ZF Grants - Browse proposals");
cy.contains(".ant-radio-wrapper", "Funding req").click();
cy.contains(
".ProposalCard",
"Fake Proposal #11 COMMUNITY FUNDING_REQUIRED"
).click();
cy.get("input[name='amountToRaise']").type("23.456");
cy.contains("button", "Fund this").click();
cy.contains("Make your contribution");
cy.contains("button", "Ive sent it").click();
cy.contains("a", "funded tab").click();
cy.contains(".ant-tabs-tab-active", "Funded");
cy.contains(
".ProfileContribution",
"Fake Proposal #11 COMMUNITY FUNDING_REQUIRED"
);
cy.contains(".ProfileContribution", "23.456");
cy.request("http://localhost:5000/api/v1/e2e/contribution/confirm");
cy.contains(
".ProfileContribution a",
"Fake Proposal #11 COMMUNITY FUNDING_REQUIRED"
).click();
cy.contains("Proposal has been funded");
cy.contains("a", "default Endtoenderson").click();
});
});

View File

@ -1,15 +0,0 @@
/// <reference types="cypress"/>
import {
loadWeb3,
increaseTime,
randomString,
syncTimeWithEvm
} from "../helpers";
// describe("sandbox", () => {
// it("how to increase time", () => {
// cy.visit("http://localhost:3000", { onBeforeLoad: loadWeb3(0) });
// // increase time on browser and ganache
// increaseTime(cy, 60000);
// });
// });

View File

@ -14,7 +14,12 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
import "./commands";
// Alternatively you can use CommonJS syntax:
// require('./commands')
Cypress.on("uncaught:exception", (err, runnable) => {
// do not stop for uncaught exceptions (wave.js)
return false;
});

View File

@ -8,13 +8,10 @@
"cypress:run": "cypress run"
},
"dependencies": {
"@cypress/webpack-preprocessor": "^2.0.1",
"@types/web3": "^1.0.3",
"cypress": "^3.1.0",
"eth-sig-util": "^2.1.0",
"@cypress/webpack-preprocessor": "^4.0.3",
"cypress": "^3.1.5",
"ts-loader": "^5.0.0",
"typescript": "^3.0.3",
"web3": "^1.0.0-beta.36",
"webpack": "^4.17.2"
}
}

File diff suppressed because it is too large Load Diff