github.com/decred/politeia@v1.4.0/politeiawww/cmd/pictl/cmdtestrun.go (about) 1 // Copyright (c) 2017-2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 // cmdTestRun performs a test run of all the politeiawww routes. 8 type cmdTestRun struct { 9 Args struct { 10 AdminEmail string `positional-arg-name:"adminemail"` 11 AdminPassword string `positional-arg-name:"adminpassword"` 12 } `positional-args:"true" required:"true"` 13 } 14 15 /* 16 var ( 17 minPasswordLength int 18 publicKey string 19 ) 20 21 // testUser stores user details that are used throughout the test run. 22 type testUser struct { 23 ID string // UUID 24 Email string // Email 25 Username string // Username 26 Password string // Password (not hashed) 27 PublicKey string // Public key of active identity 28 PaywallAddress string // Paywall address 29 PaywallAmount uint64 // Paywall amount 30 } 31 32 // login logs in the specified user. 33 func login(u testUser) error { 34 lc := shared.LoginCmd{} 35 lc.Args.Email = u.Email 36 lc.Args.Password = u.Password 37 return lc.Execute(nil) 38 } 39 40 // logout logs out current logged in user. 41 func logout() error { 42 // Logout admin 43 lc := shared.LogoutCmd{} 44 err := lc.Execute(nil) 45 if err != nil { 46 return err 47 } 48 return nil 49 } 50 51 // userRegistrationPayment ensures current logged in user has paid registration 52 // fee 53 func userRegistrationPayment() (www.UserRegistrationPaymentReply, error) { 54 urvr, err := client.UserRegistrationPayment() 55 if err != nil { 56 return www.UserRegistrationPaymentReply{}, err 57 } 58 return *urvr, nil 59 } 60 61 // randomString generates a random string 62 func randomString(length int) (string, error) { 63 b, err := util.Random(length) 64 if err != nil { 65 return "", err 66 } 67 return hex.EncodeToString(b), nil 68 } 69 70 // userNew creates a new user and returnes user's public key. 71 func userNew(email, password, username string) (*identity.FullIdentity, string, error) { 72 fmt.Printf(" Creating user: %v\n", email) 73 74 // Create user identity and save it to disk 75 id, err := shared.NewIdentity() 76 if err != nil { 77 return nil, "", err 78 } 79 80 // Setup new user request 81 nu := &www.NewUser{ 82 Email: email, 83 Username: username, 84 Password: shared.DigestSHA3(password), 85 PublicKey: hex.EncodeToString(id.Public.Key[:]), 86 } 87 nur, err := client.NewUser(nu) 88 if err != nil { 89 return nil, "", err 90 } 91 92 return id, nur.VerificationToken, nil 93 } 94 95 // userManage sends a usermanage command 96 func userManage(userID, action, reason string) error { 97 muc := shared.UserManageCmd{} 98 muc.Args.UserID = userID 99 muc.Args.Action = action 100 muc.Args.Reason = reason 101 err := muc.Execute(nil) 102 if err != nil { 103 return err 104 } 105 return nil 106 } 107 108 // userEmailVerify verifies user's email 109 func userEmailVerify(vt, email string, id *identity.FullIdentity) error { 110 fmt.Printf(" Verify user's email\n") 111 sig := id.SignMessage([]byte(vt)) 112 _, err := client.VerifyNewUser( 113 &www.VerifyNewUser{ 114 Email: email, 115 VerificationToken: vt, 116 Signature: hex.EncodeToString(sig[:]), 117 }) 118 if err != nil { 119 return err 120 } 121 return nil 122 } 123 124 // userCreate creates new user & returns the created testUser 125 func userCreate() (*testUser, *identity.FullIdentity, string, error) { 126 // Create user and verify email 127 randomStr, err := randomString(minPasswordLength) 128 if err != nil { 129 return nil, nil, "", err 130 } 131 email := randomStr + "@example.com" 132 username := randomStr 133 password := randomStr 134 id, vt, err := userNew(email, password, username) 135 if err != nil { 136 return nil, nil, "", err 137 } 138 139 return &testUser{ 140 Email: email, 141 Username: username, 142 Password: password, 143 }, id, vt, nil 144 } 145 146 // userDetals accepts a pointer to a testUser calls client's login command 147 // and stores additional information on given testUser struct 148 func userDetails(u *testUser) error { 149 // Login and store user details 150 fmt.Printf(" Login user\n") 151 lr, err := client.Login(&www.Login{ 152 Email: u.Email, 153 Password: shared.DigestSHA3(u.Password), 154 }) 155 if err != nil { 156 return err 157 } 158 159 u.PublicKey = lr.PublicKey 160 u.PaywallAddress = lr.PaywallAddress 161 u.ID = lr.UserID 162 u.PaywallAmount = lr.PaywallAmount 163 164 return nil 165 } 166 167 // testUser tests pictl user specific routes. 168 func testUserRoutes(admin testUser) error { 169 // sleepInterval is the time to wait in between requests 170 // when polling politeiawww for paywall tx confirmations. 171 const sleepInterval = 15 * time.Second 172 173 var ( 174 // paywallEnabled represents whether the politeiawww paywall 175 // has been enabled. A disabled paywall will have a paywall 176 // address of "" and a paywall amount of 0. 177 paywallEnabled bool 178 179 // numCredits is the number of proposal credits that will be 180 // purchased using the testnet faucet. 181 numCredits = 1 182 183 // Test user 184 user *testUser 185 ) 186 // Run user routes. 187 fmt.Printf("Running user routes\n") 188 189 // Create new user 190 user, id, _, err := userCreate() 191 if err != nil { 192 return err 193 } 194 195 // Resed email verification 196 fmt.Printf(" Resend email Verification\n") 197 rvr, err := client.ResendVerification(www.ResendVerification{ 198 PublicKey: hex.EncodeToString(id.Public.Key[:]), 199 Email: user.Email, 200 }) 201 if err != nil { 202 return err 203 } 204 205 // Verify email 206 err = userEmailVerify(rvr.VerificationToken, user.Email, id) 207 if err != nil { 208 return err 209 } 210 211 // Populate user's details 212 err = userDetails(user) 213 if err != nil { 214 return err 215 } 216 217 // Logout user 218 fmt.Printf(" Logout user\n") 219 err = logout() 220 if err != nil { 221 return err 222 } 223 224 // Update user key 225 err = userKeyUpdate(*user) 226 if err != nil { 227 return err 228 } 229 230 // Log back in 231 err = login(*user) 232 if err != nil { 233 return err 234 } 235 236 // Me 237 fmt.Printf(" Me\n") 238 _, err = client.Me() 239 if err != nil { 240 return err 241 } 242 243 // Edit user 244 fmt.Printf(" Edit user\n") 245 var n uint64 = 1 << 0 246 _, err = client.EditUser( 247 &www.EditUser{ 248 EmailNotifications: &n, 249 }) 250 if err != nil { 251 return err 252 } 253 254 // Change username 255 fmt.Printf(" Change username\n") 256 randomStr, err := randomString(minPasswordLength) 257 if err != nil { 258 return err 259 } 260 cuc := shared.UserUsernameChangeCmd{} 261 cuc.Args.Password = user.Password 262 cuc.Args.NewUsername = randomStr 263 err = cuc.Execute(nil) 264 if err != nil { 265 return err 266 } 267 user.Username = cuc.Args.NewUsername 268 269 // Change password 270 fmt.Printf(" Change password\n") 271 cpc := shared.UserPasswordChangeCmd{} 272 cpc.Args.Password = user.Password 273 cpc.Args.NewPassword = randomStr 274 err = cpc.Execute(nil) 275 if err != nil { 276 return err 277 } 278 user.Password = cpc.Args.NewPassword 279 280 // Reset user password 281 fmt.Printf(" Reset user password\n") 282 // Generate new random password 283 randomStr, err = randomString(minPasswordLength) 284 if err != nil { 285 return err 286 } 287 uprc := shared.UserPasswordResetCmd{} 288 uprc.Args.Email = user.Email 289 uprc.Args.Username = user.Username 290 uprc.Args.NewPassword = randomStr 291 err = uprc.Execute(nil) 292 if err != nil { 293 return err 294 } 295 user.Password = randomStr 296 297 // Login with new password 298 err = login(*user) 299 if err != nil { 300 return err 301 } 302 303 // Check if paywall is enabled. Paywall address and paywall 304 // amount will be zero values if paywall has been disabled. 305 if user.PaywallAddress != "" && user.PaywallAmount != 0 { 306 paywallEnabled = true 307 } else { 308 fmt.Printf("WARNING: politeiawww paywall is disabled\n") 309 } 310 311 // Pay user registration fee 312 if paywallEnabled { 313 // Pay user registration fee 314 fmt.Printf(" Paying user registration fee\n") 315 txID, err := util.PayWithTestnetFaucet(context.Background(), 316 cfg.FaucetHost, user.PaywallAddress, user.PaywallAmount, "") 317 if err != nil { 318 return err 319 } 320 321 dcr := float64(user.PaywallAmount) / 1e8 322 fmt.Printf(" Paid %v DCR to %v with txID %v\n", 323 dcr, user.PaywallAddress, txID) 324 } 325 326 // Wait for user registration payment confirmations 327 // If the paywall has been disable this will be marked 328 // as true. If the paywall has been enabled this will 329 // be true once the payment tx has the required number 330 // of confirmations. 331 upvr, err := userRegistrationPayment() 332 if err != nil { 333 return err 334 } 335 for !upvr.HasPaid { 336 upvr, err = userRegistrationPayment() 337 if err != nil { 338 return err 339 } 340 341 fmt.Printf(" Verify user payment: waiting for tx confirmations...\n") 342 time.Sleep(sleepInterval) 343 } 344 345 // Purchase proposal credits 346 fmt.Printf(" User proposal paywall\n") 347 ppdr, err := client.UserProposalPaywall() 348 if err != nil { 349 return err 350 } 351 352 if paywallEnabled { 353 // Purchase proposal credits 354 fmt.Printf(" Purchasing %v proposal credits\n", numCredits) 355 356 atoms := ppdr.CreditPrice * uint64(numCredits) 357 txID, err := util.PayWithTestnetFaucet(context.Background(), 358 cfg.FaucetHost, ppdr.PaywallAddress, atoms, "") 359 if err != nil { 360 return err 361 } 362 363 fmt.Printf(" Paid %v DCR to %v with txID %v\n", 364 float64(atoms)/1e8, user.PaywallAddress, txID) 365 } 366 367 // Keep track of when the pending proposal credit payment 368 // receives the required number of confirmations. 369 for { 370 pppr, err := client.UserProposalPaywallTx() 371 if err != nil { 372 return err 373 } 374 375 // TxID will be blank if the paywall has been disabled 376 // or if the payment is no longer pending. 377 if pppr.TxID == "" { 378 // Verify that the correct number of proposal credits 379 // have been added to the user's account. 380 upcr, err := client.UserProposalCredits() 381 if err != nil { 382 return err 383 } 384 385 if !paywallEnabled || len(upcr.UnspentCredits) == numCredits { 386 break 387 } 388 } 389 390 fmt.Printf(" Proposal paywall payment: waiting for tx confirmations...\n") 391 time.Sleep(sleepInterval) 392 } 393 394 // Fetch user by usernam 395 fmt.Printf(" Fetch user by username\n") 396 usersr, err := client.Users(&www.Users{ 397 Username: user.Username, 398 }) 399 if err != nil { 400 return err 401 } 402 if usersr.TotalMatches != 1 { 403 return fmt.Errorf("Wrong matching users: want %v, got %v", 1, 404 usersr.TotalMatches) 405 } 406 407 // Fetch user by public key 408 fmt.Printf(" Fetch user by public key\n") 409 usersr, err = client.Users(&www.Users{ 410 PublicKey: user.PublicKey, 411 }) 412 if err != nil { 413 return err 414 } 415 if usersr.TotalMatches != 1 { 416 return fmt.Errorf("Wrong matching users: want %v, got %v", 1, 417 usersr.TotalMatches) 418 } 419 420 // User details 421 fmt.Printf(" User details\n") 422 udc := userDetailsCmd{} 423 udc.Args.UserID = user.ID 424 err = udc.Execute(nil) 425 if err != nil { 426 return err 427 } 428 429 // Login admin 430 fmt.Printf(" Login as admin\n") 431 err = login(admin) 432 if err != nil { 433 return err 434 } 435 436 // Rescan user credits 437 fmt.Printf(" Rescan user credits\n") 438 upayrc := userPaymentsRescanCmd{} 439 upayrc.Args.UserID = user.ID 440 err = upayrc.Execute(nil) 441 if err != nil { 442 return err 443 } 444 445 // Deactivate user 446 fmt.Printf(" Deactivate user\n") 447 const userDeactivateAction = "deactivate" 448 err = userManage(user.ID, userDeactivateAction, "testing") 449 if err != nil { 450 return err 451 } 452 453 // Reactivate user 454 fmt.Printf(" Reactivate user\n") 455 const userReactivateAction = "reactivate" 456 err = userManage(user.ID, userReactivateAction, "testing") 457 if err != nil { 458 return err 459 } 460 461 // Fetch user by email 462 fmt.Printf(" Fetch user by email\n") 463 usersr, err = client.Users(&www.Users{ 464 Email: user.Email, 465 }) 466 if err != nil { 467 return err 468 } 469 if usersr.TotalMatches != 1 { 470 return fmt.Errorf("Wrong matching users: want %v, got %v", 1, 471 usersr.TotalMatches) 472 } 473 474 return nil 475 } 476 477 // proposalNewNormal is a wrapper func which creates a proposal by calling 478 // proposalNew 479 func proposalNewNormal() (*pi.ProposalNew, error) { 480 return proposalNew(false, "") 481 } 482 483 // proposalNew returns a NewProposal object contains randonly generated 484 // markdown text and a signature from the logged in user. If given `rfp` bool 485 // is true it creates an RFP. If given `linkto` it creates a RFP submission. 486 func proposalNew(rfp bool, linkto string) (*pi.ProposalNew, error) { 487 md, err := createMDFile() 488 if err != nil { 489 return nil, fmt.Errorf("create MD file: %v", err) 490 } 491 files := []pi.File{*md} 492 493 pm := www.ProposalMetadata{ 494 Name: "Some proposal name", 495 } 496 if rfp { 497 pm.LinkBy = time.Now().Add(time.Hour * 24 * 30).Unix() 498 } 499 if linkto != "" { 500 pm.LinkTo = linkto 501 } 502 pmb, err := json.Marshal(pm) 503 if err != nil { 504 return nil, err 505 } 506 metadata := []pi.Metadata{ 507 { 508 Digest: hex.EncodeToString(util.Digest(pmb)), 509 Hint: pi.HintProposalMetadata, 510 Payload: base64.StdEncoding.EncodeToString(pmb), 511 }, 512 } 513 514 sig, err := signedMerkleRoot(files, metadata, cfg.Identity) 515 if err != nil { 516 return nil, fmt.Errorf("sign merkle root: %v", err) 517 } 518 519 return &pi.ProposalNew{ 520 Files: files, 521 Metadata: metadata, 522 PublicKey: hex.EncodeToString(cfg.Identity.Public.Key[:]), 523 Signature: sig, 524 }, nil 525 } 526 527 // submitNewPropsal submits new proposal and verifies it 528 // 529 // This function returns with the user logged out 530 func submitNewProposal(user testUser) (string, error) { 531 // Login user 532 err := login(user) 533 if err != nil { 534 return "", err 535 } 536 537 fmt.Printf(" New proposal\n") 538 pn, err := proposalNewNormal() 539 if err != nil { 540 return "", err 541 } 542 pnr, err := client.ProposalNew(*pn) 543 if err != nil { 544 return "", err 545 } 546 547 // Verify proposal censorship record 548 pr := &pi.ProposalRecord{ 549 Files: pn.Files, 550 Metadata: pn.Metadata, 551 PublicKey: pn.PublicKey, 552 Signature: pn.Signature, 553 CensorshipRecord: pnr.Proposal.CensorshipRecord, 554 } 555 err = verifyProposal(*pr, publicKey) 556 if err != nil { 557 return "", fmt.Errorf("verify proposal failed: %v", err) 558 } 559 560 token := pr.CensorshipRecord.Token 561 fmt.Printf(" Proposal submitted: %v\n", token) 562 563 // Logout 564 err = logout() 565 if err != nil { 566 return "", err 567 } 568 569 return token, nil 570 } 571 572 // proposalSetStatus calls proposal set status command 573 // 574 // This function returns with the user logged out 575 func proposalSetStatus(user testUser, state pi.PropStateT, token, reason string, status pi.PropStatusT) error { 576 // Login user 577 err := login(user) 578 if err != nil { 579 return err 580 } 581 582 pssc := proposalSetStatusCmd{ 583 Unvetted: state == pi.PropStateUnvetted, 584 } 585 pssc.Args.Token = token 586 pssc.Args.Status = strconv.Itoa(int(status)) 587 pssc.Args.Reason = reason 588 err = pssc.Execute(nil) 589 if err != nil { 590 return err 591 } 592 593 return logout() 594 } 595 596 // proposalCensor censors given proposal 597 // 598 // This function returns with the user logged out 599 func proposalCensor(user testUser, state pi.PropStateT, token, reason string) error { 600 err := proposalSetStatus(user, state, token, reason, pi.PropStatusCensored) 601 if err != nil { 602 return err 603 } 604 return nil 605 } 606 607 // proposalPublic makes given proposal public 608 // 609 // This function returns with the user logged out 610 func proposalPublic(user testUser, token string) error { 611 err := proposalSetStatus(user, pi.PropStateUnvetted, token, "", pi.PropStatusPublic) 612 if err != nil { 613 return err 614 } 615 return nil 616 } 617 618 // proposalAbandon abandons given proposal 619 // 620 // This function returns with the user logged out 621 func proposalAbandon(user testUser, token, reason string) error { 622 err := proposalSetStatus(user, pi.PropStateVetted, token, reason, 623 pi.PropStatusAbandoned) 624 if err != nil { 625 return err 626 } 627 return nil 628 } 629 630 // proposalEdit edits given proposal 631 // 632 // This function returns with the user logged out 633 func proposalEdit(user testUser, state pi.PropStateT, token string) error { 634 // Login user 635 err := login(user) 636 if err != nil { 637 return err 638 } 639 640 epc := proposalEditCmd{ 641 Random: true, 642 Unvetted: state == pi.PropStateUnvetted, 643 } 644 epc.Args.Token = token 645 err = epc.Execute(nil) 646 if err != nil { 647 return err 648 } 649 650 // Logout 651 return logout() 652 } 653 654 // proposals fetchs requested proposals and verifies returned map length 655 // 656 // This function returns with the user logged out 657 func proposals(user testUser, ps pi.Proposals) (map[string]pi.ProposalRecord, error) { 658 // Login user 659 err := login(user) 660 if err != nil { 661 return nil, err 662 } 663 psr, err := client.Proposals(ps) 664 if err != nil { 665 return nil, err 666 } 667 668 if len(psr.Proposals) != len(ps.Requests) { 669 return nil, fmt.Errorf("Received wrong number of proposals: want %v,"+ 670 " got %v", len(ps.Requests), len(psr.Proposals)) 671 } 672 673 // Logout 674 err = logout() 675 if err != nil { 676 return nil, err 677 } 678 679 return psr.Proposals, nil 680 } 681 682 // userKeyUpdate updates user's key 683 // 684 // This function returns with the user logged out 685 func userKeyUpdate(user testUser) error { 686 // Login user 687 err := login(user) 688 if err != nil { 689 return err 690 } 691 692 fmt.Printf(" Update user key\n") 693 ukuc := shared.UserKeyUpdateCmd{} 694 err = ukuc.Execute(nil) 695 if err != nil { 696 return err 697 } 698 699 return logout() 700 } 701 702 // testProposalRoutes tests the propsal routes 703 func testProposalRoutes(admin testUser) error { 704 // Run proposal routes. 705 fmt.Printf("Running proposal routes\n") 706 707 // Create test user 708 fmt.Printf(" Creating test user\n") 709 user, id, vt, err := userCreate() 710 if err != nil { 711 return err 712 } 713 714 // Verify email 715 err = userEmailVerify(vt, user.Email, id) 716 if err != nil { 717 return err 718 } 719 720 // Update user key 721 err = userKeyUpdate(*user) 722 if err != nil { 723 return err 724 } 725 726 // Submit new proposal 727 censoredToken1, err := submitNewProposal(*user) 728 if err != nil { 729 return err 730 } 731 732 // Edit unvetted proposal 733 fmt.Printf(" Edit unvetted proposal\n") 734 err = proposalEdit(*user, pi.PropStateUnvetted, censoredToken1) 735 if err != nil { 736 return err 737 } 738 739 // Censor unvetted proposal 740 fmt.Printf(" Censor unvetted proposal\n") 741 const reason = "because!" 742 err = proposalCensor(admin, pi.PropStateUnvetted, censoredToken1, reason) 743 if err != nil { 744 return err 745 } 746 747 // Submit new proposal 748 censoredToken2, err := submitNewProposal(*user) 749 if err != nil { 750 return err 751 } 752 753 // Make the proposal public 754 fmt.Printf(" Set proposal status: public\n") 755 err = proposalPublic(admin, censoredToken2) 756 if err != nil { 757 return err 758 } 759 760 // Edit vetted proposal 761 fmt.Printf(" Edit vetted proposal\n") 762 err = proposalEdit(*user, pi.PropStateVetted, censoredToken2) 763 if err != nil { 764 return err 765 } 766 767 // Censor public proposal 768 fmt.Printf(" Censor public proposal\n") 769 err = proposalCensor(admin, pi.PropStateVetted, censoredToken2, reason) 770 if err != nil { 771 return err 772 } 773 774 // Submit new proposal 775 abandonedToken, err := submitNewProposal(*user) 776 if err != nil { 777 return err 778 } 779 780 // Make the proposal public 781 fmt.Printf(" Set proposal status: public\n") 782 err = proposalPublic(admin, abandonedToken) 783 if err != nil { 784 return err 785 } 786 787 // Abandon public proposal 788 fmt.Printf(" Abandon proposal\n") 789 err = proposalAbandon(admin, abandonedToken, reason) 790 if err != nil { 791 return err 792 } 793 794 // Submit new proposal and leave it unvetted 795 unvettedToken, err := submitNewProposal(*user) 796 if err != nil { 797 return err 798 } 799 800 // Submit new proposal and make it public 801 publicToken, err := submitNewProposal(*user) 802 if err != nil { 803 return err 804 } 805 806 // Make the proposal public 807 fmt.Printf(" Set proposal status: public\n") 808 err = proposalPublic(admin, publicToken) 809 if err != nil { 810 return err 811 } 812 813 // Login admin 814 err = login(admin) 815 if err != nil { 816 return err 817 } 818 819 // Proposal inventory 820 var publicExists, censoredExists, abandonedExists, unvettedExists bool 821 fmt.Printf(" Proposal inventory\n") 822 pir, err := client.ProposalInventory(pi.ProposalInventory{}) 823 if err != nil { 824 return err 825 } 826 // Vetted proposals map 827 vettedProps := pir.Vetted 828 829 // Ensure public proposal token received 830 publicProps, ok := vettedProps[pi.PropStatuses[pi.PropStatusPublic]] 831 if !ok { 832 return fmt.Errorf("No public proposals returned") 833 } 834 for _, t := range publicProps { 835 if t == publicToken { 836 publicExists = true 837 } 838 } 839 if !publicExists { 840 return fmt.Errorf("Proposal inventory missing public proposal: %v", 841 publicToken) 842 } 843 844 // Ensure vetted censored proposal token received 845 vettedCensored, ok := vettedProps[pi.PropStatuses[pi.PropStatusCensored]] 846 if !ok { 847 return fmt.Errorf("No vetted censrored proposals returned") 848 } 849 for _, t := range vettedCensored { 850 if t == censoredToken2 { 851 censoredExists = true 852 } 853 } 854 if !censoredExists { 855 return fmt.Errorf("Proposal inventory missing vetted censored proposal"+ 856 ": %v", 857 censoredToken1) 858 } 859 860 // Ensure abandoned proposal token received 861 abandonedProps, ok := vettedProps[pi.PropStatuses[pi.PropStatusAbandoned]] 862 if !ok { 863 return fmt.Errorf("No abandoned proposals returned") 864 } 865 for _, t := range abandonedProps { 866 if t == abandonedToken { 867 abandonedExists = true 868 } 869 } 870 if !abandonedExists { 871 return fmt.Errorf("Proposal inventory missing abandoned proposal: %v", 872 abandonedToken) 873 } 874 875 // Unvetted propsoals 876 unvettedProps := pir.Unvetted 877 878 // Ensure unvetted proposal token received 879 unreviewedProps, ok := unvettedProps[pi.PropStatuses[pi.PropStatusUnreviewed]] 880 if !ok { 881 return fmt.Errorf("No unreviewed proposals returned") 882 } 883 for _, t := range unreviewedProps { 884 if t == unvettedToken { 885 unvettedExists = true 886 } 887 } 888 if !unvettedExists { 889 return fmt.Errorf("Proposal inventory missing unvetted proposal: %v", 890 unvettedToken) 891 } 892 893 // Ensure unvetted censored proposal token received 894 unvettedCensored, ok := unvettedProps["censored"] 895 if !ok { 896 return fmt.Errorf("No unvetted censrored proposals returned") 897 } 898 for _, t := range unvettedCensored { 899 if t == censoredToken1 { 900 censoredExists = true 901 } 902 } 903 if !censoredExists { 904 return fmt.Errorf("Proposal inventory missing unvetted censored proposal"+ 905 ": %v", 906 censoredToken1) 907 } 908 909 // Get vetted proposals 910 fmt.Printf(" Fetch vetted proposals\n") 911 props, err := proposals(*user, pi.Proposals{ 912 State: pi.PropStateVetted, 913 Requests: []pi.ProposalRequest{ 914 { 915 Token: publicToken, 916 }, 917 { 918 Token: abandonedToken, 919 }, 920 }, 921 }) 922 if err != nil { 923 return err 924 } 925 _, publicExists = props[publicToken] 926 _, abandonedExists = props[abandonedToken] 927 if !publicExists || !abandonedExists { 928 return fmt.Errorf("Proposal batch missing requested vetted proposals") 929 } 930 931 // Get vetted proposals with short tokens 932 fmt.Printf(" Fetch vetted proposals with short tokens\n") 933 shortPublicToken := publicToken[0:7] 934 shortAbandonedToken := abandonedToken[0:7] 935 props, err = proposals(*user, pi.Proposals{ 936 State: pi.PropStateVetted, 937 Requests: []pi.ProposalRequest{ 938 { 939 Token: shortPublicToken, 940 }, 941 { 942 Token: shortAbandonedToken, 943 }, 944 }, 945 }) 946 if err != nil { 947 return err 948 } 949 _, publicExists = props[publicToken] 950 _, abandonedExists = props[abandonedToken] 951 if !publicExists || !abandonedExists { 952 return fmt.Errorf("Proposal batch missing requested vetted proposals") 953 } 954 955 // Get unvetted proposal 956 fmt.Printf(" Fetch unvetted proposal\n") 957 props, err = proposals(*user, pi.Proposals{ 958 State: pi.PropStateUnvetted, 959 Requests: []pi.ProposalRequest{ 960 { 961 Token: unvettedToken, 962 }, 963 }, 964 }) 965 if err != nil { 966 return err 967 } 968 _, unvettedExists = props[unvettedToken] 969 if !unvettedExists { 970 return fmt.Errorf("Proposal batch missing requested unvetted proposals") 971 } 972 973 // Get unvetted proposal with short token 974 fmt.Printf(" Fetch unvetted proposal with short token\n") 975 shortUnvettedToken := unvettedToken[0:7] 976 props, err = proposals(*user, pi.Proposals{ 977 State: pi.PropStateUnvetted, 978 Requests: []pi.ProposalRequest{ 979 { 980 Token: shortUnvettedToken, 981 }, 982 }, 983 }) 984 if err != nil { 985 return err 986 } 987 _, unvettedExists = props[unvettedToken] 988 if !unvettedExists { 989 return fmt.Errorf("Proposal batch missing requested unvetted proposals") 990 } 991 992 return nil 993 } 994 995 // commentNew submits a new comment 996 // 997 // This function returns with the user logged out 998 func commentNew(user testUser, state pi.PropStateT, token, comment, parentID string) error { 999 // Login user 1000 err := login(user) 1001 if err != nil { 1002 return err 1003 } 1004 1005 ncc := commentNewCmd{ 1006 Unvetted: state == pi.PropStateUnvetted, 1007 } 1008 ncc.Args.Token = token 1009 ncc.Args.Comment = comment 1010 ncc.Args.ParentID = parentID 1011 err = ncc.Execute(nil) 1012 if err != nil { 1013 return err 1014 } 1015 1016 return logout() 1017 } 1018 1019 // verifyCommentSctore accepts array of comments, a commentID and the expected 1020 // up & down votes and ensures given comment has expected score 1021 func verifyCommentScore(comments []pi.Comment, commentID uint32, upvotes, downvotes uint64) error { 1022 var commentExists bool 1023 for _, v := range comments { 1024 if v.CommentID == commentID { 1025 commentExists = true 1026 switch { 1027 case v.Upvotes != upvotes: 1028 return fmt.Errorf("comment result up votes got %v, want %v", 1029 v.Upvotes, upvotes) 1030 case v.Downvotes != downvotes: 1031 return fmt.Errorf("comment result down votes got %v, want %v", 1032 v.Downvotes, downvotes) 1033 } 1034 } 1035 } 1036 if !commentExists { 1037 return fmt.Errorf("comment not found: %v", commentID) 1038 } 1039 1040 return nil 1041 } 1042 1043 // verifyCommentVotes accepts comment votes array of all user's comment votes 1044 // on a proposals and a comment id, it verifies the number of the total votes, 1045 // the number of upvotes and the number of downvotes on given comment 1046 func verifyCommentVotes(votes []pi.CommentVoteDetails, commentID uint32, totalVotes, upvotes, downvotes int) error { 1047 var ( 1048 uvotes int 1049 dvotes int 1050 total int 1051 commentExists bool 1052 ) 1053 for _, v := range votes { 1054 if v.CommentID == commentID { 1055 commentExists = true 1056 switch v.Vote { 1057 case pi.CommentVoteDownvote: 1058 dvotes++ 1059 case pi.CommentVoteUpvote: 1060 uvotes++ 1061 } 1062 total++ 1063 } 1064 } 1065 if !commentExists { 1066 return fmt.Errorf("comment not found: %v", commentID) 1067 } 1068 if total != totalVotes { 1069 return fmt.Errorf("wrong num of comment votes got %v, want %v", 1070 total, totalVotes) 1071 } 1072 if uvotes != upvotes { 1073 return fmt.Errorf("wrong num of upvotes: got %v, want %v", 1074 uvotes, upvotes) 1075 } 1076 if dvotes != downvotes { 1077 return fmt.Errorf("wrong num of downvotes: got %v, want %v", 1078 dvotes, downvotes) 1079 } 1080 1081 return nil 1082 } 1083 1084 // testCommentRoutes tests the comment routes 1085 func testCommentRoutes(admin testUser) error { 1086 // Run commment routes. 1087 fmt.Printf("Running comment routes\n") 1088 1089 // Create test user 1090 fmt.Printf(" Creating test user\n") 1091 user, id, vt, err := userCreate() 1092 if err != nil { 1093 return err 1094 } 1095 1096 // Verify email 1097 err = userEmailVerify(vt, user.Email, id) 1098 if err != nil { 1099 return err 1100 } 1101 1102 // Update user key 1103 err = userKeyUpdate(*user) 1104 if err != nil { 1105 return err 1106 } 1107 1108 // Populate user's info 1109 err = userDetails(user) 1110 if err != nil { 1111 return err 1112 } 1113 1114 // Submit new proposal 1115 token, err := submitNewProposal(*user) 1116 if err != nil { 1117 return err 1118 } 1119 1120 // Make proposal public 1121 fmt.Printf(" Set proposal status: public\n") 1122 err = proposalPublic(admin, token) 1123 if err != nil { 1124 return err 1125 } 1126 1127 // Abandon proposal 1128 reason := "because!" 1129 fmt.Printf(" Abandon proposal\n") 1130 err = proposalAbandon(admin, token, reason) 1131 if err != nil { 1132 return err 1133 } 1134 1135 // Comment on abandoned proposal 1136 fmt.Printf(" Ensure commenting on abandoned proposal isn't allowed\n") 1137 comment := "this is a comment" 1138 err = commentNew(*user, pi.PropStateVetted, token, comment, "0") 1139 if err == nil { 1140 return fmt.Errorf("Commented on an abandoned proposal: %v", token) 1141 } 1142 1143 // Submit new proposal 1144 token, err = submitNewProposal(*user) 1145 if err != nil { 1146 return err 1147 } 1148 1149 // Censor proposal 1150 fmt.Printf(" Censor proposal\n") 1151 err = proposalCensor(admin, pi.PropStateUnvetted, token, reason) 1152 if err != nil { 1153 return err 1154 } 1155 1156 // Comment on abandoned proposal 1157 fmt.Printf(" Ensure commenting on censored proposal isn't allowed\n") 1158 err = commentNew(*user, pi.PropStateVetted, token, comment, "0") 1159 if err == nil { 1160 return fmt.Errorf("Commented on a censored proposal: %v", token) 1161 } 1162 1163 // Submit new proposal 1164 token, err = submitNewProposal(*user) 1165 if err != nil { 1166 return err 1167 } 1168 1169 // Author comment on unvetted proposal 1170 fmt.Printf(" Author comment on an unvetted proposal\n") 1171 err = commentNew(*user, pi.PropStateUnvetted, token, comment, "0") 1172 if err != nil { 1173 return err 1174 } 1175 1176 // Admin comment on an unvetted proposal 1177 fmt.Print(" Admin comment on an unvetted proposal\n") 1178 err = commentNew(admin, pi.PropStateUnvetted, token, comment, "0") 1179 if err != nil { 1180 return err 1181 } 1182 1183 // Make proposal a public 1184 fmt.Printf(" Set proposal status: public\n") 1185 err = proposalPublic(admin, token) 1186 if err != nil { 1187 return err 1188 } 1189 1190 // Author comment on a public proposal 1191 fmt.Printf(" Author comment on a public proposal\n") 1192 err = commentNew(*user, pi.PropStateVetted, token, comment, "0") 1193 if err != nil { 1194 return err 1195 } 1196 1197 // Create another user to comment 1198 fmt.Printf(" Creating another test user\n") 1199 thirdU, id, vt, err := userCreate() 1200 if err != nil { 1201 return err 1202 } 1203 1204 // Verify email 1205 err = userEmailVerify(vt, thirdU.Email, id) 1206 if err != nil { 1207 return err 1208 } 1209 1210 // Update user key 1211 err = userKeyUpdate(*thirdU) 1212 if err != nil { 1213 return err 1214 } 1215 1216 // Another user comment on a public proposal 1217 fmt.Print(" Another user comment on a public proposal\n") 1218 err = commentNew(*user, pi.PropStateVetted, token, comment, "0") 1219 if err != nil { 1220 return err 1221 } 1222 1223 // Comment on a public proposal - reply 1224 fmt.Printf(" Comment on a public proposal: reply\n") 1225 reply := "this is a comment reply" 1226 err = commentNew(*user, pi.PropStateVetted, token, reply, "1") 1227 if err != nil { 1228 return err 1229 } 1230 1231 // Validate comments 1232 fmt.Printf(" Proposal details\n") 1233 propReq := pi.ProposalRequest{ 1234 Token: token, 1235 } 1236 pdr, err := client.Proposals(pi.Proposals{ 1237 State: pi.PropStateVetted, 1238 Requests: []pi.ProposalRequest{propReq}, 1239 IncludeFiles: false, 1240 }) 1241 if err != nil { 1242 return err 1243 } 1244 prop := pdr.Proposals[token] 1245 if prop.Comments != 3 { 1246 return fmt.Errorf("proposal num comments got %v, want 3", 1247 prop.Comments) 1248 } 1249 1250 fmt.Printf(" Proposal comments\n") 1251 gcr, err := client.Comments(pi.Comments{ 1252 Token: token, 1253 State: pi.PropStateVetted, 1254 }) 1255 if err != nil { 1256 return fmt.Errorf("Comments: %v", err) 1257 } 1258 1259 if len(gcr.Comments) != 3 { 1260 return fmt.Errorf("num comments got %v, want 3", 1261 len(gcr.Comments)) 1262 } 1263 1264 for _, c := range gcr.Comments { 1265 // We check the userID because userIDs are not part of 1266 // the politeiad comment record. UserIDs are stored in 1267 // in politeiawww and are added to the comments at the 1268 // time of the request. This introduces the potential 1269 // for errors. 1270 if c.UserID != user.ID && c.CommentID == 1 { 1271 return fmt.Errorf("comment %v has wrong userID got %v, want %v", 1272 c.CommentID, c.UserID, user.ID) 1273 } 1274 } 1275 1276 // Login with admin to be able to vote on user's comments 1277 fmt.Printf(" Login admin\n") 1278 err = login(admin) 1279 if err != nil { 1280 return err 1281 } 1282 1283 // Comment vote sequence 1284 var ( 1285 // Comment actions 1286 commentActionUpvote = strconv.Itoa(int(pi.CommentVoteUpvote)) 1287 commentActionDownvote = strconv.Itoa(int(pi.CommentVoteDownvote)) 1288 ) 1289 cvc := commentVoteCmd{} 1290 cvc.Args.Token = token 1291 cvc.Args.CommentID = "1" 1292 cvc.Args.Vote = commentActionUpvote 1293 1294 fmt.Printf(" Comment vote: upvote\n") 1295 err = cvc.Execute(nil) 1296 if err != nil { 1297 return err 1298 } 1299 1300 // XXX workaround to prevent comment votes from having the same timestamp 1301 time.Sleep(time.Second) 1302 1303 fmt.Printf(" Comment vote: upvote\n") 1304 err = cvc.Execute(nil) 1305 if err != nil { 1306 return err 1307 } 1308 1309 // XXX workaround to prevent comment votes from having the same timestamp 1310 time.Sleep(time.Second) 1311 1312 fmt.Printf(" Comment vote: upvote\n") 1313 err = cvc.Execute(nil) 1314 if err != nil { 1315 return err 1316 } 1317 1318 // XXX workaround to prevent comment votes from having the same timestamp 1319 time.Sleep(time.Second) 1320 1321 fmt.Printf(" Comment vote: downvote\n") 1322 cvc.Args.Vote = commentActionDownvote 1323 err = cvc.Execute(nil) 1324 if err != nil { 1325 return err 1326 } 1327 1328 // Validate comment votes 1329 fmt.Printf(" Fetch proposal's comments & verify first comment score\n") 1330 gcr, err = client.Comments(pi.Comments{ 1331 Token: token, 1332 State: pi.PropStateVetted, 1333 }) 1334 if err != nil { 1335 return err 1336 } 1337 1338 // Verify first comment score 1339 err = verifyCommentScore(gcr.Comments, 1, 0, 1) 1340 if err != nil { 1341 return err 1342 } 1343 1344 // Validate comment votes using short token 1345 fmt.Printf(" Fetch proposal's comments using short token & verify first " + 1346 "comment score\n") 1347 gcr, err = client.Comments(pi.Comments{ 1348 Token: token[0:7], 1349 State: pi.PropStateVetted, 1350 }) 1351 if err != nil { 1352 return err 1353 } 1354 1355 // Verify first comment score 1356 err = verifyCommentScore(gcr.Comments, 1, 0, 1) 1357 if err != nil { 1358 return err 1359 } 1360 1361 fmt.Printf(" Verify admin's comment votes\n") 1362 cvr, err := client.CommentVotes(pi.CommentVotes{ 1363 State: pi.PropStateVetted, 1364 Token: token, 1365 UserID: admin.ID, 1366 }) 1367 if err != nil { 1368 return err 1369 } 1370 1371 err = verifyCommentVotes(cvr.Votes, 1, 4, 3, 1) 1372 if err != nil { 1373 return err 1374 } 1375 1376 fmt.Printf(" Verify admin's comment votes using short token\n") 1377 cvr, err = client.CommentVotes(pi.CommentVotes{ 1378 State: pi.PropStateVetted, 1379 Token: token[0:7], 1380 UserID: admin.ID, 1381 }) 1382 if err != nil { 1383 return err 1384 } 1385 1386 err = verifyCommentVotes(cvr.Votes, 1, 4, 3, 1) 1387 if err != nil { 1388 return err 1389 } 1390 1391 return nil 1392 } 1393 1394 // Execute executes the cmdTestRun command. 1395 // 1396 // This function satisfies the go-flags Commander interface. 1397 func (cmd *cmdTestRun) Execute(args []string) error { 1398 // Suppress output from cli commands 1399 cfg.Silent = true 1400 1401 fmt.Printf("Running pre-testrun validation\n") 1402 1403 // Policy 1404 fmt.Printf(" Policy\n") 1405 policy, err := client.Policy() 1406 if err != nil { 1407 return err 1408 } 1409 minPasswordLength = int(policy.MinPasswordLength) 1410 1411 // Version (CSRF tokens) 1412 fmt.Printf(" Version\n") 1413 version, err := client.Version() 1414 if err != nil { 1415 return err 1416 } 1417 publicKey = version.PubKey 1418 1419 // Verify politeiawww settings 1420 switch { 1421 case !version.TestNet: 1422 return fmt.Errorf("this command must be run on testnet") 1423 case policy.MinVoteDuration > 3: 1424 // Min vote duration must be <=3 since this command waits for 1425 // proposal votes to finish as part of the test run. 1426 return fmt.Errorf("politeiawww min vote duration is currently %v. "+ 1427 "This command requires a min vote duration of <=3 blocks. Use "+ 1428 "the politeiawww --votedurationmin flag to update this setting.", 1429 policy.MinVoteDuration) 1430 } 1431 1432 // Ensure admin credentials are valid 1433 admin := testUser{ 1434 Email: cmd.Args.AdminEmail, 1435 Password: cmd.Args.AdminPassword, 1436 } 1437 err = login(admin) 1438 if err != nil { 1439 return err 1440 } 1441 1442 // Populate admin's info 1443 err = userDetails(&admin) 1444 if err != nil { 1445 return err 1446 } 1447 1448 // Ensure admin paid registration free 1449 urpr, err := userRegistrationPayment() 1450 if err != nil { 1451 return err 1452 } 1453 if !urpr.HasPaid { 1454 return fmt.Errorf("admin has not paid registration fee") 1455 } 1456 1457 // Logout admin 1458 err = logout() 1459 if err != nil { 1460 return err 1461 } 1462 1463 // Test user routes 1464 err = testUserRoutes(admin) 1465 if err != nil { 1466 return err 1467 } 1468 1469 // Test proposal routes 1470 err = testProposalRoutes(admin) 1471 if err != nil { 1472 return err 1473 } 1474 1475 // Test comment routes 1476 err = testCommentRoutes(admin) 1477 if err != nil { 1478 return err 1479 } 1480 1481 fmt.Printf("Test run successful!\n") 1482 1483 return nil 1484 } 1485 */ 1486 1487 // testRunHelpMsg is the printed to stdout by the help command. 1488 const testRunHelpMsg = `testrun "adminusername" "adminpassword" 1489 1490 Run a series of tests on the politeiawww routes. This command can only be run 1491 on testnet. 1492 1493 Paywall: 1494 If the politeiawww paywall is enabled the test run will use the Decred tesnet 1495 faucet to pay the user registration fee and to purchase proposal credits. If 1496 the politeiawww paywall has been disabled a warning will be logged and the 1497 payments will be skipped. 1498 1499 Voting: 1500 The test run will attempt to vote on a proposal. If a dcrwallet instance is 1501 not being run locally or if the wallet does not contain any eligible tickets 1502 a warning will be logged and voting will be skipped. 1503 1504 Arguments: 1505 1. adminusername (string, required) Admin username 1506 2. adminpassword (string, required) Admin password 1507 `