Merge pull request #405 from status-im/develop

Deploy Week 15 2018
This commit is contained in:
Martin Klepsch 2018-04-12 15:21:42 +02:00 committed by GitHub
commit 462fb47b2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 540 additions and 306 deletions

View File

@ -44,5 +44,9 @@ jobs:
- run: echo $ETH_WALLET_JSON > $ETH_WALLET_FILE
# run tests!
- run: lein test
- run: lein uberjar
- store_artifacts:
path: target/uberjar/commiteth.jar
destination: commiteth.jar

View File

@ -26,6 +26,7 @@ WORKDIR /root/
RUN apt-get update
RUN apt-get -y install xvfb
RUN apt-get -y install wkhtmltopdf
RUN apt-get -y install less
COPY --from=builder /usr/src/app/target/uberjar/commiteth.jar .
COPY html2png.sh .

View File

@ -46,6 +46,7 @@ Key | Description
--- | ---
dev | Currently specifies whether Swagger UI endpoints should be added to routes
port | HTTP port for the Ring web app
dev-login | Local development only. Set it to GitHub name of your dev user in order to login into the system bypassing OAuth. `server-address` has to be then correspondingly set to your localhost address.
nrepl-port | nREPL port for development
jdbc-database-url | PostgreSQL database URL. For instance, URL to local db would be `jdbc:postgresql://localhost/commiteth?user=commiteth&password=commiteth`
server-address | URL and port of local server that can be resolved from public internet. It will be used as a redirect URI during GitHub OAuth authorization process

View File

@ -1,6 +1,6 @@
# Decisions
We record decisions. More context on why and how we do that can be found in [DR-0001](doc/decisions/0001-record-decisions.md).
We record decisions. More context on why and how we do that can be found in [DR-0001](0001-record-decisions.md).
The remainder of this document is intended to document tooling around this process.

View File

@ -1,50 +1,62 @@
# Testing Open Bounty
We have a continuously deployed version tracking the `develop` branch live at https://openbounty.status.im:444. It uses the [Ropsten](https://ropsten.io/) Ethereum testnet. Any one is welcome to use it and your help with testing Open Bounty is greatly appreciated!
We have two environments for testing purposes:
* `staging` - where `develop` branch is continuously deployed
### General
**URL:** https://openbounty.status.im:444
**GitHub app:** https://github.com/apps/status-open-bounty-app-test
* `testing`- where you can deploy and test separate pull requests
**URL:** https://testing.openbounty.status.im
**GitHub app:** https://github.com/apps/open-bounty-testing
Both of them use the [Ropsten](https://ropsten.io/) Ethereum testnet.
Any one is welcome to use it and your help with testing Open Bounty is greatly appreciated!
## General
For testing you will need:
* a web browser (Chrome is known to work, testing with others appreciated)
* an Ethereum account on the Ropsten testnet
* a Github account with administrative access to one or more repositories
* 2 Github accounts (one for Bounty Hunter, second - for Bounty Creator with administrative access to one or more repositories)
* for approving bounty payouts you will additionally need access to an Ethereum wallet. ([MetaMask](https://metamask.io/) plugin)
The developers can be reached on the `#openbounty` channel in the [Status slack](http://slack.status.im/).
### Signing up
* point your browser to https://openbounty.status.im:444 and click `Login`
Process is the same for Bounty Creator and Bounty Hunter.
* point your browser to URL of selected environment (`staging` or `testing`) and click `Login`
* Authorise status-open-bounty to have read access to your public GitHub profile.
You should now see `Bounties`, `Activity`, `Repositories` and `Manage Payouts` tabs. In the upper right hand corner, there should be a dropdown with your GitHub username and options `My Payment Details` and `Sign Out`.
You should now see `Bounties`, `Activity`, `Repositories` tabs. In the upper right hand corner, there should be a dropdown with your GitHub username and options `My Payment Details` and `Sign Out`.
## For Bounty Creator
### Connecting your wallet
(instructions for Metamask)
* install Metamask and configure your account
* select `My Payment Details` from the top-right dropdown, select the account you want to use from the selection list and click `Update`
### Creating bounty issues
Before you can create bounties, you need to add Open Bounty GitHub App to your account or repos. Go to https://github.com/apps/status-open-bounty-app-test (or link to another GitHub App you've created for testing, as described in the [README](README.md) and click Install. Specify whether access to all org repos or specific repos is granted. This will install webhooks for SOB in your repos.
Before you can create bounties, you need to add Open Bounty GitHub App to your account or repos. Go to **GitHub app** for selected environment(or link to another GitHub App you've created for testing, as described in the [README](README.md) and click Install. Specify whether access to all org repos or specific repos is granted. This will install webhooks for SOB in your repos.
* Request for your account to be whitelisted. Contact [Riot](https://chat.status.im) for more information
* now, add the `bounty` label to a new or an existing issue. This should cause Status Open Bounty to post a new comment for the issue containing an image with text `Deploying contract, please wait`
* once the contract has been mined, the comment will be updated to contain the bounty contract's address and a QR code
* in SOB `Manage Payouts` should appear (when you logged in)
### Funding bounties
The Github comment has a QR code as an image containing the bounty contract address. The address is also on the comment as text. Use any ethereum wallet to send ETH and/or supported ERC20 tokens to this address. After a small delay (max 5 minutes), the activity feed should show that the related bounty issue's balance increased and comment should be updated.
### Submitting claims
To get bounties you need to provide an Ethereum address in you Payment details on the https://openbounty.status.im:444 that will be used to send bounties to.
Open a pull request against the target repository with `Fixes: #NN` in the comment where `NN` is the issue number of the bountied Github issue. After the PR has been opened, the activity feed should show an item indicating that your username has opened a claim for the related bounty issue. The repository admin should also see the claim under `Open claims` in the `Manage payouts` view.
After this process bounty is available for BountyHunters on URL of selected environment in `Open Bounty` list.
### Managing payouts
@ -52,8 +64,20 @@ Repository admins see a listing of all open claims and bounties that have alread
### Removing bounties
To remove issue from the Bounties list you can close it in GitHub.
To remove issue from the Bounties list you can close it in GitHub.
## For Bounty Hunter
### Submitting claims
Whole process is explained here: [Status Open Bounty Tutorial](https://www.youtube.com/watch?v=vTjcXP4kTHc).
To get bounties you need to provide an Ethereum address in you Payment details on the selected that will be used to send bounties to.
Open a pull request against the target repository with any keyword from [Closing issues using keywords](https://help.github.com/articles/closing-issues-using-keywords/), i. e. `Fixes: #NN` in the comment where `NN` is the issue number of the bountied Github issue.
After the PR has been opened, the `Activity` feed should show an item indicating that your username has opened a claim for the related bounty issue. The repository admin (Bounty Creator) should also see the claim under `Open claims` in the `Manage payouts` view.
Flow walkthrough for Bounty Creator and Bounty Contributor is [here](https://docs.google.com/presentation/d/1btWVeaqR6yPLSHHZQ2XgfgK8MU2tWuAtNUE_5hdfJCI/edit#slide=id.g314ca9a4e1_0_0).
### Reporting bugs
All bugs should be reported as issues in the [OpenBounty Github repository](https://github.com/status-im/open-bounty/issues).

View File

@ -0,0 +1,7 @@
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pkey;
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pr_id_key;
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_fkey;
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pkey PRIMARY KEY (pr_id);
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pr_id_key UNIQUE (pr_id);

View File

@ -0,0 +1,6 @@
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pkey;
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pr_id_key;
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pkey PRIMARY KEY (pr_id, issue_id);
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pr_id_key UNIQUE (pr_id, issue_id);
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_fkey FOREIGN KEY (issue_id) REFERENCES issues(issue_id);

View File

@ -43,6 +43,13 @@ SELECT *
FROM users
WHERE id = :id;
-- :name get-user-by-login :? :1
-- :doc retrieve a user given GitHub login.
SELECT *
FROM users
WHERE login = :login;
-- :name get-repo-owner :? :1
SELECT *
FROM users u, repositories r
@ -213,7 +220,7 @@ VALUES(:pr_id,
:commit_sha,
:user_id,
:state)
ON CONFLICT (pr_id) DO UPDATE
ON CONFLICT (pr_id,issue_id) DO UPDATE
SET
state = :state,
issue_number = :issue_number,

View File

@ -22,24 +22,22 @@
(defn deploy-contract [owner owner-address repo issue-id issue-number]
(if (empty? owner-address)
(log/error "Unable to deploy bounty contract because"
"repo owner has no Ethereum addres")
(log/errorf "issue %s: Unable to deploy bounty contract because repo owner has no Ethereum addres" issue-id)
(do
(log/info "deploying contract to " owner-address)
(log/infof "issue %s: Deploying contract to %s" issue-id owner-address)
(if-let [transaction-hash (multisig/deploy-multisig owner-address)]
(do
(log/info "Contract deployed, transaction-hash:"
transaction-hash)
(log/infof "issue %s: Contract deployed, transaction-hash: %s" issue-id transaction-hash)
(let [resp (github/post-deploying-comment owner
repo
issue-number
transaction-hash)
_ (log/info "post-deploying-comment response:" resp)
comment-id (:id resp)]
(log/infof "issue %s: post-deploying-comment response: %s" issue-id resp)
(issues/update-comment-id issue-id comment-id))
(issues/update-transaction-hash issue-id transaction-hash))
(log/error "Failed to deploy contract to" owner-address)))))
(log/errorf "issue %s Failed to deploy contract to %s" issue-id owner-address)))))
(defn add-bounty-for-issue [repo repo-id issue]
(let [{issue-id :id
issue-number :number
@ -47,16 +45,17 @@
created-issue (issues/create repo-id issue-id issue-number issue-title)
{owner-address :address
owner :owner} (users/get-repo-owner repo-id)]
(log/debug "Adding bounty for issue " repo issue-number "owner address: " owner-address)
(log/debug "issue %s: Adding bounty for issue %s/%s - owner address: %s"
issue-id repo issue-number owner-address)
(if (= 1 created-issue)
(deploy-contract owner owner-address repo issue-id issue-number)
(log/debug "Issue already exists in DB, ignoring"))))
(log/debug "issue %s: Issue already exists in DB, ignoring"))))
(defn maybe-add-bounty-for-issue [repo repo-id issue]
(let [res (issues/get-issues-count repo-id)
{count :count} res
limit-reached? (> count max-issues-limit)
_ (log/debug "*** get-issues-count" repo-id " " res " " count " " limit-reached?)]
limit-reached? (> count max-issues-limit)]
(log/debug "*** get-issues-count" repo-id " " res " " count " " limit-reached?)
(if limit-reached?
(log/debug "Total issues for repo limit reached " repo " " count)
(add-bounty-for-issue repo repo-id issue))))
@ -102,9 +101,7 @@
(issues/get-issue-titles)]
(let [gh-issue (github/get-issue owner repo issue_number)]
(if-not (= title (:title gh-issue))
(do
(log/info "Updating changed title for issue" (:id gh-issue))
(issues/update-issue-title (:id gh-issue) (:title gh-issue)))))))
(issues/update-issue-title (:id gh-issue) (:title gh-issue))))))
(defn assert-keys [m ks]
(doseq [k ks]

View File

@ -1,7 +1,8 @@
(ns commiteth.db.issues
(:require [commiteth.db.core :refer [*db*] :as db]
[clojure.java.jdbc :as jdbc]
[clojure.set :refer [rename-keys]]))
[clojure.set :refer [rename-keys]]
[clojure.tools.logging :as log]))
(defn create
"Creates issue"
@ -31,6 +32,7 @@
(defn update-issue-title
[issue-id title]
(log/info "issue %s: Updating changed title \"%s\"" issue-id title)
(jdbc/with-db-connection [con-db *db*]
(db/update-issue-title con-db {:issue_id issue-id
:title title})))

View File

@ -21,6 +21,11 @@
(jdbc/with-db-connection [con-db *db*]
(db/get-user con-db {:id user-id})))
(defn get-user-by-login
[login]
(jdbc/with-db-connection [con-db *db*]
(db/get-user-by-login con-db {:login login})))
(defn exists?
[user-id]
(jdbc/with-db-connection [con-db *db*]

View File

@ -74,11 +74,19 @@
signed (TransactionEncoder/signMessage tx (creds))
hex-string (Numeric/toHexString signed)]
hex-string))
(defn eth-gasstation-gas-price
"Get max of average and average_calc from gas station and use that
as gas price. average_calc is computed from a larger time period than average,
so the idea is to account for both temporary dips (when average_calc > average)
and temporary rises (average_calc < average) of gas price"
[]
(let [data (json-api-request "https://ethgasstation.info/json/ethgasAPI.json")
avg-price (-> (get data "average")
bigint)
avg-price (max
(-> (get data "average")
bigint)
(-> (get data "average_calc")
bigint))
avg-price-gwei (/ avg-price (bigint 10))]
(->> (* (bigint (Math/pow 10 9)) avg-price-gwei) ;; for some reason the API returns 10x gwei price
.toBigInteger)))

View File

@ -23,7 +23,10 @@
:on-testnet? (env :on-testnet)}))
(defn landing-page []
(layout/render "index.html" {:authorize-url (github/signup-authorize-url)}))
(layout/render "index.html"
{:authorize-url (if (env :dev-login)
(str (env :server-address) "/callback_dev")
(github/signup-authorize-url))}))
(defn welcome-page []
(layout/render "welcome.html"))

View File

@ -35,28 +35,33 @@
(defroutes redirect-routes
(GET "/callback" [code state]
(let [resp (github/post-for-token code state)
body (keywordize-keys (codec/form-decode (:body resp)))
scope (:scope body)
access-token (:access_token body)]
(log/info "access-token:" access-token)
(log/debug "github sign-in callback, response body:" body)
(if (:error body)
;; Why does Mist browser sends two redirects at the same time? The latter results in 401 error.
(found (str (env :server-address) "/app"))
(let [admin-token? (str/includes? scope "repo")
token-key (if admin-token? :admin-token :token)
gh-user (github/get-user access-token)
new-user? (nil? (users/get-user (:id gh-user 0)))
user (assoc (get-or-create-user access-token)
token-key access-token)]
(when (and (hubspot-contact-create-enabled)
new-user?)
(try
(hubspot/create-hubspot-contact (:email user)
(:name user "")
(:login user))
(catch Throwable t
(log/error "Failed to create hubspot contact" t))))
(assoc (found (str (env :server-address) "/app"))
:session {:identity user}))))))
(let [resp (github/post-for-token code state)
body (keywordize-keys (codec/form-decode (:body resp)))
scope (:scope body)
access-token (:access_token body)]
(log/info "access-token:" access-token)
(log/debug "github sign-in callback, response body:" body)
(if (:error body)
;; Why does Mist browser sends two redirects at the same time? The latter results in 401 error.
(found (str (env :server-address) "/app"))
(let [admin-token? (str/includes? scope "repo")
token-key (if admin-token? :admin-token :token)
gh-user (github/get-user access-token)
new-user? (nil? (users/get-user (:id gh-user 0)))
user (assoc (get-or-create-user access-token)
token-key access-token)]
(when (and (hubspot-contact-create-enabled)
new-user?)
(try
(hubspot/create-hubspot-contact (:email user)
(:name user "")
(:login user))
(catch Throwable t
(log/error "Failed to create hubspot contact" t))))
(assoc (found (str (env :server-address) "/app"))
:session {:identity user})))))
(GET "/callback_dev" []
(assoc (found (str (env :server-address) "/app"))
:session {:identity (users/get-user-by-login (env :dev-login))}))
)

View File

@ -174,24 +174,23 @@
pr-body :body
pr-title :title} :pull_request}]
(log/info "handle-pull-request-event" event-type owner repo repo-id login pr-body pr-title)
(if-let [issue (some->> (extract-issue-number owner repo pr-body pr-title)
(first)
(issues/get-issue repo-id))]
(if-not (:commit_sha issue) ; no PR has been merged yet referencing this issue
(do
(log/info "Referenced bounty issue found" owner repo (:issue_number issue))
(handle-claim issue
user-id
login name
avatar_url
owner repo
repo-id
pr-id
pr-number
head-sha
merged?
event-type))
(log/info "PR for issue already merged"))
(if-let [issues (remove nil? (map #(issues/get-issue repo-id %1) (extract-issue-number owner repo pr-body pr-title)))]
(doseq [issue issues]
(if-not (:commit_sha issue) ; no PR has been merged yet referencing this issue
(do
(log/info "Referenced bounty issue found" owner repo (:issue_number issue))
(handle-claim issue
user-id
login name
avatar_url
owner repo
repo-id
pr-id
pr-number
head-sha
merged?
event-type))
(log/info "PR for issue already merged")))
(when (= :edited event-type)
; Remove PR if it does not reference any issue
(pull-requests/remove pr-id))))

View File

@ -38,6 +38,7 @@
(update-balances)))
)
(defn update-issue-contract-address
"For each pending deployment: gets transaction receipt, updates db
state (contract-address, comment-id) and posts github comment"
@ -45,56 +46,57 @@
(log/info "In update-issue-contract-address")
(p :update-issue-contract-address
(doseq [{issue-id :issue_id
transaction-hash :transaction_hash} (issues/list-pending-deployments)]
(log/info "pending deployment:" transaction-hash)
(try
(when-let [receipt (eth/get-transaction-receipt transaction-hash)]
(log/info "update-issue-contract-address: transaction receipt for issue #"
issue-id ": " receipt)
(if-let [contract-address (multisig/find-created-multisig-address receipt)]
(let [issue (issues/update-contract-address issue-id contract-address)
{owner :owner
repo :repo
comment-id :comment_id
issue-number :issue_number} issue
balance-eth-str (eth/get-balance-eth contract-address 6)
balance-eth (read-string balance-eth-str)]
(log/info "Updating comment image")
(bounties/update-bounty-comment-image issue-id
owner
repo
issue-number
contract-address
balance-eth
balance-eth-str
{})
(log/info "Updating comment")
(github/update-comment owner
repo
comment-id
issue-number
contract-address
balance-eth
balance-eth-str
{}))
(log/error "Failed to find contract address in tx logs")))
(catch Throwable ex
(do (log/error "update-issue-contract-address exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))))
transaction-hash :transaction_hash} (issues/list-pending-deployments)]
(log/infof "issue %s: pending deployment: %s" issue-id transaction-hash)
(try
(when-let [receipt (eth/get-transaction-receipt transaction-hash)]
(log/infof "issue %s: update-issue-contract-address: tx receipt: %s" issue-id receipt)
(if-let [contract-address (multisig/find-created-multisig-address receipt)]
(let [issue (issues/update-contract-address issue-id contract-address)
{owner :owner
repo :repo
comment-id :comment_id
issue-number :issue_number} issue
balance-eth-str (eth/get-balance-eth contract-address 6)
balance-eth (read-string balance-eth-str)]
(log/infof "issue %s: Updating comment image" issue-id)
(bounties/update-bounty-comment-image issue-id
owner
repo
issue-number
contract-address
balance-eth
balance-eth-str
{})
(log/infof "issue %s: Updating comment" issue-id)
(github/update-comment owner
repo
comment-id
issue-number
contract-address
balance-eth
balance-eth-str
{}))
(log/errorf "issue %s: Failed to find contract address in tx logs" issue-id)))
(catch Throwable ex
(log/errorf ex "issue %s: update-issue-contract-address exception:" issue-id)))))
(log/info "Exit update-issue-contract-address"))
(defn deploy-pending-contracts
"Under high-concurrency circumstances or in case geth is in defunct state, a bounty contract may not deploy successfully when the bounty label is addded to an issue. This function deploys such contracts."
[]
(p :deploy-pending-contracts
(doseq [{issue-id :issue_id
issue-number :issue_number
owner :owner
owner-address :owner_address
repo :repo} (db-bounties/pending-contracts)]
(log/debug "Trying to re-deploy failed bounty contract deployment, issue-id:" issue-id)
(bounties/deploy-contract owner owner-address repo issue-id issue-number))))
(p :deploy-pending-contracts
(doseq [{issue-id :issue_id
issue-number :issue_number
owner :owner
owner-address :owner_address
repo :repo} (db-bounties/pending-contracts)]
(log/infof "issue %s: Trying to re-deploy failed bounty contract deployment" issue-id)
(try
(bounties/deploy-contract owner owner-address repo issue-id issue-number)
(catch Throwable ex
(log/errorf ex "issue %s: deploy-pending-contracts exception:" issue-id))))))
(defn self-sign-bounty
"Walks through all issues eligible for bounty payout and signs corresponding transaction"
@ -110,37 +112,35 @@
issue-number :issue_number
balance-eth :balance_eth
tokens :tokens
winner-login :winner_login} (db-bounties/pending-bounties)]
(try
(let [value (eth/get-balance-hex contract-address)]
(if (empty? payout-address)
(do
(log/error "Cannot sign pending bounty - winner has no payout address")
(github/update-merged-issue-comment owner
repo
comment-id
contract-address
(eth-decimal->str balance-eth)
tokens
winner-login
true))
(let [execute-hash (multisig/send-all contract-address payout-address)]
(log/info "Payout self-signed, called sign-all(" contract-address payout-address ") tx:" execute-hash)
(db-bounties/update-execute-hash issue-id execute-hash)
(db-bounties/update-winner-login issue-id winner-login)
(github/update-merged-issue-comment owner
repo
comment-id
contract-address
(eth-decimal->str balance-eth)
tokens
winner-login
false))))
(catch Throwable ex
(do (log/error "self-sign-bounty exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))))
(log/info "Exit self-sign-bounty")
)
winner-login :winner_login} (db-bounties/pending-bounties)]
(try
(let [value (eth/get-balance-hex contract-address)]
(if (empty? payout-address)
(do
(log/warn "issue %s: Cannot sign pending bounty - winner has no payout address" issue-id)
(github/update-merged-issue-comment owner
repo
comment-id
contract-address
(eth-decimal->str balance-eth)
tokens
winner-login
true))
(let [execute-hash (multisig/send-all contract-address payout-address)]
(log/infof "issue %s: Payout self-signed, called sign-all(%s) tx: %s" issue-id contract-address payout-address execute-hash)
(db-bounties/update-execute-hash issue-id execute-hash)
(db-bounties/update-winner-login issue-id winner-login)
(github/update-merged-issue-comment owner
repo
comment-id
contract-address
(eth-decimal->str balance-eth)
tokens
winner-login
false))))
(catch Throwable ex
(log/error ex "issue %s: self-sign-bounty exception" issue-id)))))
(log/info "Exit self-sign-bounty"))
(defn update-confirm-hash
"Gets transaction receipt for each pending payout and updates DB confirm_hash with tranaction ID of commiteth bot account's confirmation."
@ -148,13 +148,16 @@
(log/info "In update-confirm-hash")
(p :update-confirm-hash
(doseq [{issue-id :issue_id
execute-hash :execute_hash} (db-bounties/pending-payouts)]
(log/info "pending payout:" execute-hash)
(when-let [receipt (eth/get-transaction-receipt execute-hash)]
(log/info "execution receipt for issue #" issue-id ": " receipt)
(when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)]
(log/info "confirm hash:" confirm-hash)
(db-bounties/update-confirm-hash issue-id confirm-hash)))))
execute-hash :execute_hash} (db-bounties/pending-payouts)]
(log/infof "issue %s: pending payout: %s" issue-id execute-hash)
(try
(when-let [receipt (eth/get-transaction-receipt execute-hash)]
(log/infof "issue %s: execution receipt for issue " issue-id receipt)
(when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)]
(log/infof "issue %s: confirm hash:" issue-id confirm-hash)
(db-bounties/update-confirm-hash issue-id confirm-hash)))
(catch Throwable ex
(log/errorf ex "issue %s: update-confirm-hash exception:" issue-id)))))
(log/info "Exit update-confirm-hash"))
@ -163,10 +166,13 @@
[]
(p :update-watch-hash
(doseq [{issue-id :issue_id
watch-hash :watch_hash} (db-bounties/pending-watch-calls)]
(log/info "pending watch call" watch-hash)
(when-let [receipt (eth/get-transaction-receipt watch-hash)]
(db-bounties/update-watch-hash issue-id nil)))))
watch-hash :watch_hash} (db-bounties/pending-watch-calls)]
(log/infof "issue %s: pending watch call %s" issue-id watch-hash)
(try
(when-let [receipt (eth/get-transaction-receipt watch-hash)]
(db-bounties/update-watch-hash issue-id nil))
(catch Throwable ex
(log/errorf ex "issue %s: update-watch-hash exception:" issue-id))))))
(defn older-than-3h?
@ -194,7 +200,7 @@
confirm-id :confirm_hash
payee-login :payee_login
updated :updated} (db-bounties/confirmed-payouts)]
(log/debug "confirmed payout:" payout-hash)
(log/infof "issue %s: confirmed payout: %s" issue-id payout-hash)
(try
(if-let [receipt (eth/get-transaction-receipt payout-hash)]
(let [contract-tokens (multisig/token-balances contract-address)
@ -203,14 +209,14 @@
(some #(> (second %) 0.0) contract-tokens)
(> contract-eth-balance 0))
(do
(log/info "Contract still has funds")
(log/infof "issue %s: Contract (%s) still has funds" issue-id contract-address)
(when (multisig/is-confirmed? contract-address confirm-id)
(log/info "Detected bounty with funds and confirmed payout, calling executeTransaction")
(log/infof "issue %s: Detected bounty with funds and confirmed payout, calling executeTransaction" issue-id)
(let [execute-tx-hash (multisig/execute-tx contract-address confirm-id)]
(log/info "execute tx:" execute-tx-hash))))
(log/infof "issue %s: execute tx: %s" issue-id execute-tx-hash))))
(do
(log/info "Payout has succeeded, saving payout receipt for issue #" issue-id ": " receipt)
(log/infof "issue %s: Payout has succeeded, payout receipt %s" issue-id receipt)
(db-bounties/update-payout-receipt issue-id receipt)
(github/update-paid-issue-comment owner
repo
@ -220,13 +226,11 @@
tokens
payee-login))))
(when (older-than-3h? updated)
(log/info "Resetting payout hash for issue" issue-id "as it has not been mined in 3h")
(log/warn "issue %s: Resetting payout hash for issue as it has not been mined in 3h" issue-id)
(db-bounties/reset-payout-hash issue-id)))
(catch Throwable ex
(do (log/error "update-payout-receipt exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))))
(log/info "Exit update-payout-receipt")
)
(catch Throwable ex
(log/error ex "issue %s: update-payout-receipt exception" issue-id)))))
(log/info "Exit update-payout-receipt"))
(defn abs
"(abs n) is the absolute value of n"
@ -239,26 +243,27 @@
(defn update-bounty-token-balances
"Helper function for updating internal ERC20 token balances to token multisig contract. Will be called periodically for all open bounty contracts."
"Helper function for updating internal ERC20 token balances to token
multisig contract. Will be called periodically for all open bounty
contracts."
[issue-id bounty-addr watch-hash]
#_(log/info "In update-bounty-token-balances for issue" issue-id)
(log/info "In update-bounty-token-balances for issue" issue-id)
(doseq [[tla token-data] (token-data/as-map)]
(try
(let [balance (multisig/token-balance bounty-addr tla)]
(when (> balance 0)
(do
(log/info "bounty at" bounty-addr "has" balance "of token" tla)
(log/infof "bounty %s: has %s of token %s" bounty-addr balance tla)
(let [internal-balance (multisig/token-balance-in-bounty bounty-addr tla)]
(when (and (nil? watch-hash)
(not= balance internal-balance))
(log/info "balances not in sync, calling watch")
(log/infof "bounty %s: balances not in sync, calling watch" bounty-addr)
(let [hash (multisig/watch-token bounty-addr tla)]
(db-bounties/update-watch-hash issue-id hash)))))))
(catch Throwable ex
(do (log/error "update-bounty-token-balances exception:" ex)
(clojure.stacktrace/print-stack-trace ex)))))
#_(log/info "Exit update-bounty-token-balances"))
(catch Throwable ex
(log/error ex "bounty %s: update-bounty-token-balances exception" bounty-addr))))
(log/info "Exit update-bounty-token-balances"))
(defn update-contract-internal-balances
"It is required in our current smart contract to manually update it's internal balance when some tokens have been added."
@ -296,8 +301,8 @@
[]
(p :update-open-issue-usd-values
(doseq [{bounty-addr :contract_address}
(db-bounties/open-bounty-contracts)]
(update-issue-usd-value bounty-addr))))
(db-bounties/open-bounty-contracts)]
(update-issue-usd-value bounty-addr))))
(defn float=
([x y] (float= x y 0.0000001))
@ -327,7 +332,7 @@
(let [balance-eth-str (eth/get-balance-eth contract-address 6)
balance-eth (read-string balance-eth-str)
token-balances (multisig/token-balances contract-address)]
(log/debug "update-balances" balance-eth
(log/debug "issue" issue-id ": update-balances" balance-eth
balance-eth-str token-balances owner repo issue-number)
(when (or
@ -361,8 +366,7 @@
token-balances)
(update-issue-usd-value contract-address))))
(catch Throwable ex
(do (log/error "update-balances exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))))
(log/error ex "issue %s: update-balances exception" issue-id)))))
(log/info "Exit update-balances"))
@ -370,7 +374,7 @@
(try
(func)
(catch Throwable t
(log/error t))))
(log/error t (.getMessage t) (ex-data t)))))
(defn run-tasks [tasks]
(doall

View File

@ -13,7 +13,8 @@
:as rf-storage
:refer [reg-co-fx!]]
[commiteth.ui-model :as ui-model]
[commiteth.common :as common]))
[commiteth.common :as common]
[commiteth.routes :as routes]))
(rf-storage/reg-co-fx! :commiteth-sob {:fx :store
@ -40,6 +41,20 @@
(println "redirecting to" path)
(set! (.-pathname js/location) path)))
(reg-fx
:persist-bounty-filters-in-query
(fn [{:keys [bounty-filters]}]
(let [query
(->> bounty-filters
(remove (comp nil? val))
(map (fn [[k v]]
[(ui-model/bounty-filter-type->query-param k)
(ui-model/bounty-filter-value->query-param k v)]))
(into {}))]
(routes/nav! :bounties {} (if (= {} query)
nil
query)))))
(reg-event-fx
:initialize-db
[(inject-cofx :store)]
@ -65,10 +80,17 @@
(reg-event-db
:set-active-page
(fn [db [_ page]]
(fn [db [_ page params query]]
(assoc db :page page
:page-number 1
::db/open-bounties-filters {}
::db/open-bounties-filters
(reduce-kv
#(let [type (ui-model/query-param->bounty-filter-type %2)]
(assoc %1
type
(ui-model/query-param->bounty-filter-value type %3)))
{}
query)
::db/open-bounties-sorting-type ::ui-model/bounty-sorting-type|most-recent)))
(reg-event-db
@ -468,9 +490,12 @@
(merge db {::db/open-bounties-sorting-type sorting-type
:page-number 1})))
(reg-event-db
::set-open-bounty-filter-type
(fn [db [_ filter-type filter-value]]
(-> db
(assoc-in [::db/open-bounties-filters filter-type] filter-value)
(assoc :page-number 1))))
(reg-event-fx
::set-open-bounty-filter-type
(fn [{:keys [event db]} [_ filter-type filter-value]]
(println "db" db)
(let [filters (::db/open-bounties-filters db)]
(println "filters" filters)
{:persist-bounty-filters-in-query
{:bounty-filters
(assoc filters filter-type filter-value)}})))

View File

@ -14,12 +14,17 @@
"A function which will be called on each route change."
[name params query]
(println "Route change to: " name params query)
(rf/dispatch [:set-active-page name]))
(rf/dispatch [:set-active-page name params query]))
(defn setup-nav! []
(bide/start! router {:default :bounties
:on-navigate on-navigate}))
(defn nav! [route-id]
(bide/navigate! router route-id {}))
(defn nav!
([route-id]
(nav! route-id nil))
([route-id params]
(nav! route-id params nil))
([route-id params query]
(bide/navigate! router route-id params query)))

View File

@ -179,7 +179,7 @@
(mapcat keys)
(filter identity)
set)]
(into #{"ETH"} token-ids))))
(into #{:ETH} token-ids))))
(reg-sub
::filtered-and-sorted-open-bounties

View File

@ -87,11 +87,11 @@
::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options
::bounty-filter-type.re-frame-subs-key-for-options :commiteth.subscriptions/open-bounties-currencies
::bounty-filter-type.predicate (fn [filter-value bounty]
(or (and (contains? filter-value "ETH")
(or (and (contains? filter-value :ETH)
(< 0 (js/parseFloat (:balance-eth bounty))))
(not-empty
(set/intersection
(->> filter-value (remove #{"ETH"}) set)
(->> filter-value (remove #{:ETH}) set)
(-> bounty :tokens keys set)))))}
::bounty-filter-type|date
@ -124,6 +124,50 @@
(defn bounty-filter-type->name [filter-type]
(-> bounty-filter-types-def (get filter-type) ::bounty-filter-type.name))
(defn bounty-filter-type->query-param [filter-type]
(-> filter-type
name
(clojure.string/replace #".*\|" "")))
(defn bounty-filter-value->query-param [type value]
(let [category (-> bounty-filter-types-def type ::bounty-filter-type.category)]
(cond
(= category ::bounty-filter-type-category|multiple-dynamic-options)
(vec value)
(= category ::bounty-filter-type-category|single-static-option)
(bounty-filter-type->query-param value)
(= category ::bounty-filter-type-category|range)
(str (first value) "-" (second value)))))
(defn query-param->bounty-filter-type [query-param]
(keyword (namespace ::_) (str "bounty-filter-type|" (name query-param))))
(defn query-param->bounty-filter-value [type value]
(let [category (-> bounty-filter-types-def type ::bounty-filter-type.category)]
(cond
(= type ::bounty-filter-type|currency)
(if (string? value)
#{(keyword value)}
(set (map keyword value)))
(= type ::bounty-filter-type|owner)
(if (string? value)
#{value}
(set value))
(= type ::bounty-filter-type|claims)
(keyword (namespace ::_)
(str "bounty-filter-type-claims-option|" (name value)))
(= type ::bounty-filter-type|date)
(keyword (namespace ::_)
(str "bounty-filter-type-date-option|" (name value)))
(= category ::bounty-filter-type-category|range)
(clojure.string/split value #"-"))))
(defn bounty-filter-value->short-text [filter-type filter-value]
(cond
(= filter-type ::bounty-filter-type|date)

4
test/Jenkinsfile vendored
View File

@ -2,11 +2,11 @@ node ('linux1') {sauce('1be1b688-e0e7-4314-92a0-db11f52d3c00') {
checkout([$class: 'GitSCM', branches: [[name: '*/develop']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], submoduleCfg: [], userRemoteConfigs: [[url: 'https://github.com/status-im/open-bounty.git']]])
configFileProvider([configFile(fileId: 'sob_automation_test_config', targetLocation: 'test/end-to-end/tests')]) {
try {withCredentials([string(credentialsId: 'SOB_SAUCE_ACCESS_KEY', variable: 'SAUCE_ACCESS_KEY'), string(credentialsId: 'SOB_SAUCE_USERNAME', variable: 'SAUCE_USERNAME')])
{sh 'cd test && docker build -t end2end . && docker run --rm -e "SAUCE_USERNAME="${SAUCE_USERNAME} -e "SAUCE_ACCESS_KEY="${SAUCE_ACCESS_KEY} --name end2end-container end2end -m pytest -m sanity --build=$BUILD_NAME -v -n 1'}
{sh 'cd test && docker build -t end2end . && docker run -v `pwd`/end-to-end/tests/results:/app/results --rm -e "SAUCE_USERNAME="${SAUCE_USERNAME} -e "SAUCE_ACCESS_KEY="${SAUCE_ACCESS_KEY} --name end2end-container end2end -m pytest -m sanity --build=$BUILD_NAME -v -n 1'}
}
finally {
saucePublisher()
junit testDataPublishers: [[$class: 'SauceOnDemandReportPublisher', jobVisibility: 'public']], testResults: 'test/end-to-end/tests/*.xml' }
junit testDataPublishers: [[$class: 'SauceOnDemandReportPublisher', jobVisibility: 'public']], testResults: 'test/end-to-end/tests/results/*.xml' }
}
}
}

View File

@ -6,7 +6,6 @@ from selenium.webdriver.support import expected_conditions
class BaseElement(object):
class Locator(object):
def __init__(self, by, value):

View File

@ -15,4 +15,3 @@ class BasePageObject(object):
@property
def time_now(self):
return datetime.now().strftime('%-m%-d%-H%-M%-S')

View File

@ -0,0 +1,25 @@
from pages.base_page import BasePageObject
from pages.base_element import *
from tests import test_data
import logging
class ActivityDescription(BaseText):
def __init__(self, driver, status, issue_title):
super(ActivityDescription, self).__init__(driver)
self.locator = self.Locator.xpath_selector(
'//div[@class="description"]/div[contains(.,"%s")]/a[contains(.,"%s")]' % (status, issue_title))
class ActivityPage(BasePageObject):
def __init__(self, driver):
super(ActivityPage, self).__init__(driver)
self.driver = driver
def get_activity_page(self):
self.driver.get(test_data.config['Common']['url'] + 'app#/activity')
def check_activity_is_presented(self, status, issue_title):
logging.info('Check that activity "%s %s" is displayed' % (status, issue_title))
ActivityDescription(self.driver, status, issue_title).find_element()

View File

@ -1,5 +1,6 @@
import logging
from pages.base_page import BasePageObject
from pages.base_element import *
from pages.base_element import BaseText
from tests import test_data
@ -16,6 +17,7 @@ class TopHuntersHeader(BaseText):
super(TopHuntersHeader, self).__init__(driver)
self.locator = self.Locator.css_selector('.top-hunters-header')
class BountyTitles(BaseText):
def __init__(self, driver):
@ -29,6 +31,7 @@ class BountyItemRows(BaseText):
super(BountyItemRows, self).__init__(driver)
self.locator = self.Locator.css_selector('.open-bounty-item-content .bounty-item-row')
class BountyFooters(BaseText):
def __init__(self, driver):
@ -36,6 +39,14 @@ class BountyFooters(BaseText):
self.locator = self.Locator.css_selector('.open-bounty-item-content .footer-row')
class BountyClaimsAmount(BaseText):
def __init__(self, driver, issue_title, claims_text):
super(BaseText, self).__init__(driver)
self.locator = self.Locator.xpath_selector(
'//div[@class="header"]/a[contains(.,"%s")]/../../div[@class="footer-row"]/span[contains(.,"%s")]' % (issue_title, claims_text))
class BountiesPage(BasePageObject):
def __init__(self, driver):
super(BountiesPage, self).__init__(driver)
@ -51,3 +62,6 @@ class BountiesPage(BasePageObject):
def get_bounties_page(self):
self.driver.get(test_data.config['Common']['url'] + 'app')
def check_bounty_claims_amount(self, issue_title, claims_text):
logging.info('Check that bounty "%s" has "%s"' % (issue_title, claims_text))
BountyClaimsAmount(self.driver, issue_title, claims_text).find_element()

View File

@ -1,5 +1,5 @@
from pages.base_page import BasePageObject
from pages.base_element import *
from pages.base_element import BaseButton
from tests import test_data

View File

@ -1,11 +1,8 @@
import time, pytest
from pages.base_element import *
import time, pytest, git, os, shutil, logging
from selenium.common.exceptions import TimeoutException
from pages.base_element import BaseEditBox, BaseButton, BaseText
from pages.base_page import BasePageObject
from tests import test_data
from git import Repo
import os
import shutil
import logging
class EmailEditbox(BaseEditBox):
@ -110,25 +107,30 @@ class ContractBody(BaseText):
super(ContractBody, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//tbody//p[contains(text(), "
"'Current balance: 0.000000 ETH')]")
class IssueId(BaseText):
def __init__(self, driver):
super(IssueId, self).__init__(driver)
self.locator = self.Locator.css_selector(".gh-header-number")
class ForkButton(BaseButton):
def __init__(self, driver):
super(ForkButton, self).__init__(driver)
self.locator = self.Locator.css_selector("[href='#fork-destination-box']")
class HeaderInForkPopup(BaseText):
def __init__(self, driver):
super(HeaderInForkPopup, self).__init__(driver)
self.locator = self.Locator.css_selector("#facebox-header")
class UserAccountInForkPopup(BaseButton):
def __init__(self, driver):
super(UserAccountInForkPopup, self).__init__(driver)
self.locator = self.Locator.css_selector("[value=%s]"%test_data.config['DEV']['gh_username'])
self.locator = self.Locator.css_selector("[value=%s]" % test_data.config['DEV']['gh_username'])
class ForkedRepoText(BaseText):
@ -136,20 +138,45 @@ class ForkedRepoText(BaseText):
super(ForkedRepoText, self).__init__(driver)
self.locator = self.Locator.css_selector(".commit-tease")
class DeleteRepo(BaseButton):
def __init__(self, driver):
super(DeleteRepo, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//button[text()[contains(.,' Delete this repository')]]")
class RepoNameBoxInPopup(BaseEditBox):
def __init__(self, driver):
super(RepoNameBoxInPopup, self).__init__(driver)
self.locator = self.Locator.css_selector("input[aria-label='Type in the name of the repository to confirm that you want to delete this repository.']")
self.locator = self.Locator.css_selector(
"input[aria-label='Type in the name of the repository to confirm that you want to delete this repository.']")
class ConfirmDeleteButton(BaseButton):
def __init__(self, driver):
super(ConfirmDeleteButton, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//button[text()[contains(.,'I understand the consequences, delete')]]")
self.locator = self.Locator.xpath_selector(
"//button[text()[contains(.,'I understand the consequences, delete')]]")
class CompareAndPullRequest(BaseButton):
def __init__(self, driver):
super(CompareAndPullRequest, self).__init__(driver)
self.locator = self.Locator.css_selector(".RecentBranches a")
class PrTitleEditBox(BaseEditBox):
def __init__(self, driver):
super(PrTitleEditBox, self).__init__(driver)
self.locator = self.Locator.id("pull_request_body")
class SubmitNewPrButton(BaseButton):
def __init__(self, driver):
super(SubmitNewPrButton, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//button[contains(text(), "
"'Create pull request')]")
class GithubPage(BasePageObject):
def __init__(self, driver):
@ -157,18 +184,18 @@ class GithubPage(BasePageObject):
self.driver = driver
# sign in and installation related
self.email_input = EmailEditbox(self.driver)
self.password_input = PasswordEditbox(self.driver)
self.sign_in_button = SignInButton(self.driver)
self.authorize_sob = AuthorizeStatusOpenBounty(self.driver)
self.permission_type = PermissionTypeText(self.driver)
self.install_button = InstallButton(self.driver)
self.organization_button = OrganizationButton(self.driver)
self.all_repositories_button = AllRepositoriesButton(self.driver)
self.integration_permissions_group = IntegrationPermissionsGroup(self.driver)
# issue related
self.new_issue_button = NewIssueButton(self.driver)
self.issue_title_input = IssueTitleEditBox(self.driver)
self.labels_button = LabelsButton(self.driver)
@ -177,21 +204,28 @@ class GithubPage(BasePageObject):
self.submit_new_issue_button = SubmitNewIssueButton(self.driver)
self.contract_body = ContractBody(self.driver)
self.issue_id = IssueId(self.driver)
# repo forking
self.fork_button = ForkButton(self.driver)
self.header_in_fork_popup = HeaderInForkPopup(self.driver)
self.user_account_in_fork_popup = UserAccountInForkPopup(self.driver)
self.forked_repo_text = ForkedRepoText(self.driver)
# repo deleting
self.delete_repo = DeleteRepo(self.driver)
self.repo_name_confirm_delete = RepoNameBoxInPopup(self.driver)
self.confirm_delete = ConfirmDeleteButton(self.driver)
# PR related
self.compare_and_pull_request = CompareAndPullRequest(self.driver)
self.pr_body = PrTitleEditBox(self.driver)
self.submit_new_pr_button = SubmitNewPrButton(self.driver)
def get_issues_page(self):
self.driver.get(test_data.config['ORG']['gh_repo'] + 'issues')
self.driver.get('%sissues' % test_data.config['ORG']['gh_repo'])
def get_issue_page(self, issue_id):
self.driver.get(test_data.config['ORG']['gh_repo'] + 'issues/' + issue_id)
self.driver.get('%sissues/%s' % (test_data.config['ORG']['gh_repo'], issue_id))
def get_sob_plugin_page(self):
self.driver.get(test_data.config['Common']['sob_test_app'])
@ -201,7 +235,6 @@ class GithubPage(BasePageObject):
self.password_input.send_keys(password)
self.sign_in_button.click()
def install_sob_plugin(self):
initial_url = self.driver.current_url
self.get_sob_plugin_page()
@ -215,14 +248,14 @@ class GithubPage(BasePageObject):
self.get_issues_page()
self.new_issue_button.click()
test_data.issue = dict()
test_data.issue['title'] = 'auto_test_bounty_%s' % self.time_now
test_data.issue['title'] = 'auto_test_bounty_%s' % test_data.date_time
self.issue_title_input.send_keys(test_data.issue['title'])
self.labels_button.click()
self.bounty_label.click()
self.cross_button.click()
self.submit_new_issue_button.click()
test_data.issue['id'] = self.issue_id.text[1:]
logging.info("Issue title is %s" % test_data.issue['title'])
logging.info("Issue title is %s" % test_data.issue['title'])
def fork_repo(self, initial_repo, wait=60):
self.driver.get(initial_repo)
@ -242,35 +275,49 @@ class GithubPage(BasePageObject):
except TimeoutException:
time.sleep(10)
pass
pytest.fail('Contract is not deployed in %s minutes!' % str(wait/60))
pytest.fail('Contract is not deployed in %s minutes!' % str(wait / 60))
#cloning via HTTPS
def clone_repo(self, initial_repo=None, username=None, repo_name=None, repo_path='git_repo'):
os.mkdir(repo_path)
os.chdir(repo_path)
test_data.local_repo_path = os.getcwd()
# cloning via HTTPS
def clone_repo(self, initial_repo=None, username=None, repo_name=None, repo_folder='test_repo'):
os.mkdir(repo_folder)
os.chdir(repo_folder)
self.local_repo_path = os.getcwd()
fork = 'https://github.com/%s/%s.git' % (username, repo_name)
logging.info(('Cloning from %s to %s' % (fork, repo_path)))
r = Repo.clone_from(fork, repo_path)
logging.info(('Successefully cloned to: %s' % test_data.local_repo_path))
logging.info('Set upstream to %s'% initial_repo)
upstream = r.create_remote('upstream', initial_repo)
logging.info(('Cloning from %s to %s' % (fork, self.local_repo_path)))
repo = git.Repo.clone_from(fork, self.local_repo_path)
logging.info(('Successefully cloned to: %s' % self.local_repo_path))
logging.info('Set upstream to %s' % initial_repo)
upstream = repo.create_remote('upstream', initial_repo)
upstream.fetch()
assert upstream.exists()
r.heads.master.checkout()
repo.heads.master.checkout()
def create_pr_git(self, branch, file_to_modify='test'):
repo = git.Repo(self.local_repo_path)
logging.info(repo.git.status())
logging.info(repo.git.pull('upstream', 'master'))
logging.info(repo.git.push('origin', 'master'))
logging.info(repo.git.fetch('--all'))
repo.git.checkout('-b', branch)
file = open(os.path.join(self.local_repo_path, file_to_modify), 'w')
file.write("Autotest change: %s \r \n" % test_data.date_time)
logging.info(repo.git.add('test'))
logging.info(repo.git.commit(m='Aut %s' % test_data.date_time))
repo.git.push('origin', branch)
def open_pr_github(self, keyword_comment):
self.get_url(test_data.config['DEV']['gh_forked_repo'])
self.compare_and_pull_request.click()
self.pr_body.send_keys('%s #%s' % (keyword_comment, test_data.issue['id']))
self.submit_new_pr_button.click()
def clean_repo_local_folder(self):
logging.info('Removing %s' % test_data.local_repo_path)
if test_data.local_repo_path:
shutil.rmtree(test_data.local_repo_path)
logging.info('Removing %s' % self.local_repo_path)
if self.local_repo_path:
shutil.rmtree(self.local_repo_path)
def delete_fork(self):
self.get_url(test_data.config['DEV']['gh_forked_repo'] + 'settings')
self.delete_repo.click()
self.repo_name_confirm_delete.send_keys(test_data.config['ORG']['gh_repo_name'])
self.confirm_delete.click()

View File

@ -1,6 +1,6 @@
import time
from pages.base_page import BasePageObject
from pages.base_element import *
from pages.base_element import BaseButton, BaseText, BaseEditBox
from selenium.webdriver import ActionChains
@ -74,7 +74,6 @@ class MetaMaskPlugin(BasePageObject):
self.ok_button = OkButton(self.driver)
def recover_access(self, passphrase, password, confirm_password):
self.get_url('chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/popup.html')
self.accept_button.click()
ActionChains(self.driver).move_to_element(self.privacy_text.find_element()).perform()
@ -85,4 +84,3 @@ class MetaMaskPlugin(BasePageObject):
self.password_edit_box.send_keys(password)
self.password_box_confirm.send_keys(confirm_password)
self.ok_button.click()

View File

@ -1,3 +1,3 @@
[pytest]
norecursedirs = .git pages
addopts = -s -v --junitxml=result.xml --tb=short
addopts = -s -v --junitxml=results/result.xml --tb=short

View File

@ -1,5 +1,5 @@
import configparser
import os
import configparser, time, datetime, os
class TestData(object):
@ -7,15 +7,17 @@ class TestData(object):
self.test_name = None
self.config = configparser.ConfigParser()
# define here path to your config.ini file
# example - config_example.ini
# put config.ini to /test/end-to-end/tests folder (same directory where config_example.ini is placed
self.tests_path = os.path.abspath(os.path.dirname(__file__))
self.config.read(os.path.join(self.tests_path, 'config.ini'))
self.config.read(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'config.ini'))
# create unique identificator for PRs, issues ect
ts = time.time()
st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
self.date_time = st
# self.issue['title'] is set in GithubPage::create_new_bounty
# self.issue['id'] is set in GithubPage::create_new_bounty
# self.local_repo_path is set in GithubPage::clone_repo
test_data = TestData()

View File

@ -1,19 +1,20 @@
import pytest, sys
import pytest, sys, os
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from tests.postconditions import remove_application, remove_installation
from os import environ, path
from os import environ
from tests import test_data
from pages.thirdparty.github import GithubPage
from pages.openbounty.landing import LandingPage
class BaseTestCase:
class BaseTestCase:
def print_sauce_lab_info(self, driver):
sys.stdout = sys.stderr
print("SauceOnDemandSessionID=%s job-name=%s" % (driver.session_id,
pytest.config.getoption('build')))
def get_remote_caps(self):
sauce_lab_cap = dict()
sauce_lab_cap['name'] = test_data.test_name
@ -36,11 +37,11 @@ class BaseTestCase:
@classmethod
def setup_class(cls):
cls.errors = []
cls.environment = pytest.config.getoption('env')
cls.environment = pytest.config.getoption('env')
###################################################################################################################
######### Drivers setup
###################################################################################################################
################################################################################################################
######### Drivers setup
################################################################################################################
#
# Dev Chrome options
@ -52,20 +53,20 @@ class BaseTestCase:
# Org Chrome options
#
cls.capabilities_org = webdriver.ChromeOptions()
# doesn't work on sauce env
# cls.capabilities_org.add_extension(path.abspath(test_data.config['Paths']['tests_absolute'] + 'resources/metamask3_12_0.crx'))
cls.capabilities_org.add_extension(
os.path.join(test_data.tests_path, os.pardir, 'resources', 'metamask3_12_0.crx'))
#
# SauceLab capabilities
#
cls.executor_sauce_lab = 'http://%s:%s@ondemand.saucelabs.com:80/wd/hub' % (
environ.get('SAUCE_USERNAME'), environ.get('SAUCE_ACCESS_KEY'))
drivers = []
environ.get('SAUCE_USERNAME'), environ.get('SAUCE_ACCESS_KEY'))
cls.drivers = []
if cls.environment == 'local':
for caps in cls.capabilities_dev, cls.capabilities_org:
driver = webdriver.Chrome(chrome_options=caps)
drivers.append(driver)
cls.drivers.append(driver)
if cls.environment == 'sauce':
for caps in cls.capabilities_dev, cls.capabilities_org:
@ -74,30 +75,26 @@ class BaseTestCase:
new_caps.update(remote)
driver = webdriver.Remote(cls.executor_sauce_lab,
desired_capabilities=new_caps)
drivers.append(driver)
cls.drivers.append(driver)
for driver in drivers:
cls.print_sauce_lab_info(cls, driver)
cls.driver_dev = cls.drivers[0]
cls.driver_org = cls.drivers[1]
cls.driver_dev = drivers[0]
cls.driver_org = drivers[1]
for driver in cls.drivers:
driver.implicitly_wait(10)
for driver in drivers:
driver.implicitly_wait(10)
###################################################################################################################
######### Actions for each driver before class
###################################################################################################################
################################################################################################################
######### Actions for each driver before class
################################################################################################################
######ORG
landing = LandingPage(cls.driver_org)
landing.get_landing_page()
# Sign Up to SOB
# Sign Up to SOB
cls.github_org = landing.login_button.click()
cls.github_org.sign_in(test_data.config['ORG']['gh_login'],
test_data.config['ORG']['gh_password'])
test_data.config['ORG']['gh_password'])
assert cls.github_org.permission_type.text == 'Personal user data'
bounties_page = cls.github_org.authorize_sob.click()
@ -111,21 +108,17 @@ class BaseTestCase:
# Sign In to GH as Developer
cls.github_dev.get_login_page()
cls.github_dev.sign_in(test_data.config['DEV']['gh_login'],
test_data.config['DEV']['gh_password'])
test_data.config['DEV']['gh_password'])
# Fork repo as Developer from Organization
# Fork repo as Developer from Organization
cls.github_dev.fork_repo(test_data.config['ORG']['gh_repo'])
# Cloning repo to local git as Developer and set upstream to Organization (via HTTPS)
# Cloning repo to local git as Developer and set upstream to Organization (via HTTPS)
cls.github_dev.clone_repo(test_data.config['ORG']['gh_repo'],
test_data.config['DEV']['gh_username'],
test_data.config['ORG']['gh_repo_name'],
'git_repo')
test_data.config['DEV']['gh_username'],
test_data.config['ORG']['gh_repo_name'])
cls.verify_no_errors(cls)
@classmethod
def teardown_class(cls):
@ -140,10 +133,9 @@ class BaseTestCase:
cls.github_dev.delete_fork()
try:
cls.driver_dev.quit()
cls.driver_org.quit()
for driver in cls.drivers:
cls.print_sauce_lab_info(cls, driver)
driver.quit()
except WebDriverException:
pass

View File

@ -20,4 +20,3 @@ def remove_installation(driver):
driver.find_element(By.CSS_SELECTOR, '.facebox-popup .btn-danger').click()
except NoSuchElementException:
pass

View File

View File

@ -1,8 +1,6 @@
import pytest
from os import environ
from pages.openbounty.landing import LandingPage
from pages.openbounty.bounties import BountiesPage
from pages.thirdparty.github import GithubPage
from pages.openbounty.activity import ActivityPage
from tests.basetestcase import BaseTestCase
from tests import test_data
@ -10,18 +8,32 @@ from tests import test_data
@pytest.mark.sanity
class TestLogin(BaseTestCase):
def test_deploy_new_contract(self):
def test_deploy_new_contract(self):
# Waiting for deployed contract; test_data.issue created here
self.github_org.create_new_bounty()
self.github_org.get_deployed_contract()
# Navigate and check top bounty in "Open bounties"
bounties_page = BountiesPage(self.driver_org)
bounties_page = BountiesPage(self.driver_dev)
bounties_page.get_bounties_page()
titles = bounties_page.bounty_titles.find_elements()
assert titles[0].text == test_data.issue['title']
def test_new_claim(self):
self.github_dev.create_pr_git('test_branch_%s' % self.github_dev.time_now)
self.github_dev.open_pr_github('Fixes')
# check new claim in "Open bounties"
bounties_page = BountiesPage(self.driver_dev)
bounties_page.get_bounties_page()
bounties_page.check_bounty_claims_amount(test_data.issue['title'], '1 open claim')
# check new claim in "Activity"
activity_page = ActivityPage(self.driver_dev)
activity_page.get_activity_page()
activity_page.check_activity_is_presented('Submitted a claim for ', test_data.issue['title'])