github.com/decred/politeia@v1.4.0/politeiawww/cmd/cmswww/testrun.go (about) 1 // Copyright (c) 2020 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 import ( 8 "encoding/base64" 9 "encoding/binary" 10 "encoding/hex" 11 "encoding/json" 12 "fmt" 13 "strconv" 14 "time" 15 16 "github.com/decred/dcrd/chaincfg/v3" 17 "github.com/decred/dcrd/hdkeychain/v3" 18 "github.com/decred/politeia/politeiad/api/v1/mime" 19 "github.com/decred/politeia/politeiad/backend/gitbe/cmsplugin" 20 cms "github.com/decred/politeia/politeiawww/api/cms/v1" 21 www "github.com/decred/politeia/politeiawww/api/www/v1" 22 "github.com/decred/politeia/politeiawww/cmd/shared" 23 "github.com/decred/politeia/util" 24 ) 25 26 // TestRunCmd performs a test run of cmswww routes. 27 type TestRunCmd struct { 28 Args struct { 29 AdminEmail string `positional-arg-name:"adminemail"` 30 AdminPassword string `positional-arg-name:"adminpassword"` 31 } `positional-args:"true" required:"true"` 32 } 33 34 const ( 35 // DCC support/oppose options. These are not the all contractor 36 // vote options. Those are defined in cmsplugin. 37 dccIssuanceSupport = "aye" 38 dccIssuanceOppose = "nay" 39 ) 40 41 // user represents a cms user that is used during the test run. 42 type user struct { 43 ID string 44 Email string 45 Username string 46 Password string 47 AddressIndex uint32 // Used to generate unique payment addresses 48 } 49 50 func (u *user) paymentAddress() (string, error) { 51 xpub := "tpubVobLtToNtTq6TZNw4raWQok35PRPZou53vegZqNubtBTJMMFmuMpWybF" + 52 "CfweJ52N8uZJPZZdHE5SRnBBuuRPfC5jdNstfKjiAs8JtbYG9jx" 53 54 if u.AddressIndex == 0 { 55 // The address index is set to be the uint32 representation of 56 // the hex username, which was randomly generated, so that we 57 // don't have payment address collisions between the different 58 // test users. CMS doesn't allow the same payment address to 59 // be used more than a single time. 60 b, err := hex.DecodeString(u.Username) 61 if err != nil { 62 return "", err 63 } 64 u.AddressIndex = binary.LittleEndian.Uint32(b) 65 } 66 67 // Increment the address index for the user so that a unique 68 // payment address is generated each time. 69 u.AddressIndex += 1 70 71 // The index isn't allowed to be larger than the HardenKeyStart. 72 // See hdkeychain ExtendedKey.Child() for more info. 73 if u.AddressIndex >= hdkeychain.HardenedKeyStart { 74 u.AddressIndex = u.AddressIndex % hdkeychain.HardenedKeyStart 75 } 76 77 return util.DeriveChildAddress(chaincfg.TestNet3Params(), 78 xpub, u.AddressIndex) 79 } 80 81 func randomString() string { 82 b, err := util.Random(16) 83 if err != nil { 84 return "uh oh, randomString() failed" 85 } 86 return hex.EncodeToString(b) 87 } 88 89 // login logs the given user into politeiawww. The full LoginCmd must be used 90 // instead of just calling client.Login() so that the persistent CLI data is 91 // updated properly. 92 func login(u user) error { 93 lc := shared.LoginCmd{} 94 lc.Args.Email = u.Email 95 lc.Args.Password = u.Password 96 return lc.Execute(nil) 97 } 98 99 // logout logs out whatever user is currently logged in with the CLI. The full 100 // LogoutCmd must be used instead of just calling client.Logout() so that the 101 // persistent CLI data is updated properly. 102 func logout() error { 103 l := shared.LogoutCmd{} 104 return l.Execute(nil) 105 } 106 107 // userNew returns a new cms user that has been invited and registered. The 108 // user credentials are randomly generated. 109 // 110 // This function returns with the admin logged out. 111 func userNew(admin user) (*user, error) { 112 // Login the admin 113 err := login(admin) 114 if err != nil { 115 return nil, fmt.Errorf("login: %v", err) 116 } 117 118 // Generate random user credentials 119 b, err := util.Random(www.PolicyMinPasswordLength) 120 if err != nil { 121 return nil, err 122 } 123 u := user{ 124 Email: hex.EncodeToString(b) + "@example.com", 125 Username: hex.EncodeToString(b), 126 Password: hex.EncodeToString(b), 127 } 128 129 // Invite user 130 inu := cms.InviteNewUser{ 131 Email: u.Email, 132 } 133 inur, err := client.InviteNewUser(&inu) 134 if err != nil { 135 return nil, fmt.Errorf("InviteNewUser: %v", err) 136 } 137 if inur.VerificationToken == "" { 138 return nil, fmt.Errorf("InviteNewUserReply: verification token not " + 139 "found; the politeiawww email server likely needs to be disabled") 140 } 141 142 // Register user. Use the full command so that the identity is 143 // saved to disk. This will also log the user in. 144 r := RegisterUserCmd{} 145 r.Args.Email = u.Email 146 r.Args.Username = u.Username 147 r.Args.Password = u.Password 148 r.Args.Token = inur.VerificationToken 149 err = r.Execute(nil) 150 if err != nil { 151 return nil, fmt.Errorf("RegisterUserCmd: %v", err) 152 } 153 154 // Get the user ID 155 lr, err := client.Me() 156 if err != nil { 157 return nil, fmt.Errorf("Me: %v", err) 158 } 159 u.ID = lr.UserID 160 161 // Log the user out 162 err = logout() 163 if err != nil { 164 return nil, fmt.Errorf("logout: %v", err) 165 } 166 167 return &u, nil 168 } 169 170 // contractorNew creates a new user then updates the user with the given 171 // contractor details. 172 // 173 // This function returns with the admin logged out. 174 func contractorNew(admin user, dt cms.DomainTypeT, ct cms.ContractorTypeT) (*user, error) { 175 // Invite and register a new user 176 u, err := userNew(admin) 177 if err != nil { 178 return nil, fmt.Errorf("userNew: %v", err) 179 } 180 181 err = login(admin) 182 if err != nil { 183 return nil, fmt.Errorf("login: %v", err) 184 } 185 186 // Update the user's contractor status 187 muc := CMSManageUserCmd{ 188 Domain: strconv.Itoa(int(dt)), 189 ContractorType: strconv.Itoa(int(ct)), 190 } 191 muc.Args.UserID = u.ID 192 err = muc.Execute(nil) 193 if err != nil { 194 return nil, fmt.Errorf("CMSManageUserCmd: %v", err) 195 } 196 197 err = logout() 198 if err != nil { 199 return nil, fmt.Errorf("logout: %v", err) 200 } 201 202 return u, err 203 } 204 205 // invoiceNew submits a new invoice for the provided user. The invoice is 206 // submitted for the most recent month that the user has not submitted an 207 // invoice for. 208 // 209 // contractorRate is in USD. 210 // labor is in hours. 211 // 212 // This function returns with the user logged out. 213 func invoiceNew(u user, contractorRate uint, labor float64) (*www.CensorshipRecord, error) { 214 err := login(u) 215 if err != nil { 216 return nil, fmt.Errorf("login: %v", err) 217 } 218 219 // Get user's previous invoices 220 uir, err := client.UserInvoices(&cms.UserInvoices{}) 221 if err != nil { 222 return nil, fmt.Errorf("UserInvoices: %v", err) 223 } 224 225 // Find the first available date that we can submit an invoice 226 // for. 227 invoiceDates := make(map[uint]map[uint]struct{}) // [year][month]struct{} 228 for _, v := range uir.Invoices { 229 _, ok := invoiceDates[v.Input.Year] 230 if !ok { 231 invoiceDates[v.Input.Year] = make(map[uint]struct{}, 12) 232 } 233 invoiceDates[v.Input.Year][v.Input.Month] = struct{}{} 234 } 235 236 y, m, _ := time.Now().AddDate(0, -1, 0).Date() 237 var ( 238 month = uint(m) // Last month 239 year = uint(y) // Current year 240 found bool 241 ) 242 for !found { 243 _, ok := invoiceDates[year] 244 if !ok { 245 found = true 246 continue 247 } 248 _, ok = invoiceDates[year][month] 249 if !ok { 250 found = true 251 continue 252 } 253 254 // Decrement month 255 switch { 256 case month > 1: 257 month -= 1 258 case month == 1: 259 month = 12 260 year -= 1 261 default: 262 return nil, fmt.Errorf("invalid date") 263 } 264 } 265 266 // Submit an invoice for the first available month 267 ier := cms.InvoiceExchangeRate{ 268 Month: month, 269 Year: year, 270 } 271 ierr, err := client.InvoiceExchangeRate(&ier) 272 if err != nil { 273 return nil, fmt.Errorf("InvoiceExchangeRate %v: %v", 274 ier, err) 275 } 276 address, err := u.paymentAddress() 277 if err != nil { 278 return nil, err 279 } 280 ii := cms.InvoiceInput{ 281 Month: month, 282 Year: year, 283 ExchangeRate: ierr.ExchangeRate, 284 ContractorName: u.Username, 285 ContractorLocation: "Mars", 286 ContractorContact: u.Email, 287 ContractorRate: contractorRate * 100, // In cents 288 PaymentAddress: address, 289 LineItems: []cms.LineItemsInput{ 290 { 291 Type: cms.LineItemTypeLabor, 292 Domain: "Development", 293 Subdomain: "politeia", 294 Description: "PR 999: politeiawww: Add stuff.", 295 ProposalToken: "", 296 SubUserID: "", 297 SubRate: 0, 298 Labor: uint(labor * 60), // In minutes 299 Expenses: 0, 300 }, 301 }, 302 } 303 b, err := json.Marshal(ii) 304 if err != nil { 305 return nil, err 306 } 307 files := []www.File{ 308 { 309 Name: "invoice.json", 310 MIME: mime.DetectMimeType(b), 311 Digest: hex.EncodeToString(util.Digest(b)), 312 Payload: base64.StdEncoding.EncodeToString(b), 313 }, 314 } 315 sig, err := signedMerkleRoot(files, nil, cfg.Identity) 316 if err != nil { 317 return nil, err 318 } 319 ni := &cms.NewInvoice{ 320 Files: files, 321 PublicKey: hex.EncodeToString(cfg.Identity.Public.Key[:]), 322 Signature: sig, 323 Month: month, 324 Year: year, 325 } 326 nir, err := client.NewInvoice(ni) 327 if err != nil { 328 return nil, fmt.Errorf("NewInvoice: %v", err) 329 } 330 331 // Verify the censorship record 332 vr, err := client.Version() 333 if err != nil { 334 return nil, fmt.Errorf("Version: %v", err) 335 } 336 ir := cms.InvoiceRecord{ 337 Files: ni.Files, 338 PublicKey: ni.PublicKey, 339 Signature: ni.Signature, 340 CensorshipRecord: nir.CensorshipRecord, 341 } 342 err = verifyInvoice(ir, vr.PubKey) 343 if err != nil { 344 return nil, fmt.Errorf("unable to verify invoice %v: %v", 345 ir.CensorshipRecord.Token, err) 346 } 347 348 // Log the user out 349 err = logout() 350 if err != nil { 351 return nil, fmt.Errorf("logout: %v", err) 352 } 353 354 return &nir.CensorshipRecord, nil 355 } 356 357 // invoiceSetStatus sets the status of the provided invoice. 358 // 359 // Changing an invoic's status to InvoiceStatusPaid is not a valid status 360 // transition. The invoicesPay() function must be used instead if you want 361 // to manually mark invoices as paid. 362 // 363 // This function returns with the admin logged out. 364 func invoiceSetStatus(admin user, token string, s cms.InvoiceStatusT) error { 365 err := login(admin) 366 if err != nil { 367 return fmt.Errorf("login: %v", err) 368 } 369 370 // Get the most recent version of the DCC to pull the version 371 // from it. 372 idr, err := client.InvoiceDetails(token, nil) 373 if err != nil { 374 return fmt.Errorf("InvoiceDetails: %v", err) 375 } 376 377 // Set the invoice status 378 c := SetInvoiceStatusCmd{} 379 c.Args.Version = idr.Invoice.Version 380 c.Args.Token = token 381 c.Args.Status = strconv.Itoa(int(s)) 382 c.Args.Reason = "some reason...." 383 err = c.Execute(nil) 384 if err != nil { 385 return fmt.Errorf("SetInvoiceStatusCmd: %v", err) 386 } 387 388 err = logout() 389 if err != nil { 390 return fmt.Errorf("logout: %v", err) 391 } 392 393 return nil 394 } 395 396 // invoicesPays marks all of the currently approved invoices as paid. 397 // 398 // This function returns with the admin logged out. 399 func invoicesPay(admin user) error { 400 err := login(admin) 401 if err != nil { 402 return fmt.Errorf("login: %v", err) 403 } 404 405 _, err = client.PayInvoices(&cms.PayInvoices{}) 406 if err != nil { 407 return fmt.Errorf("PayInvoices: %v", err) 408 } 409 410 err = logout() 411 if err != nil { 412 return fmt.Errorf("logout: %v", err) 413 } 414 415 return nil 416 } 417 418 // dccNew submits a new DCC to cmswww then returns the submitted DCC. 419 // 420 // This function returns with the user logged out. 421 func dccNew(sponsor user, nomineeID string, dcct cms.DCCTypeT, dt cms.DomainTypeT, ct cms.ContractorTypeT) (*cms.DCCRecord, error) { 422 err := login(sponsor) 423 if err != nil { 424 return nil, fmt.Errorf("login: %v", err) 425 } 426 427 // We can't use the NewDCCCmd here because we need the 428 // censorship token that is returned in the reply. Run 429 // the command manually. 430 di := cms.DCCInput{ 431 Type: dcct, 432 NomineeUserID: nomineeID, 433 Domain: dt, 434 ContractorType: ct, 435 SponsorStatement: "this person is good", 436 } 437 b, err := json.Marshal(di) 438 if err != nil { 439 return nil, err 440 } 441 f := www.File{ 442 Name: "dcc.json", 443 MIME: mime.DetectMimeType(b), 444 Digest: hex.EncodeToString(util.Digest(b)), 445 Payload: base64.StdEncoding.EncodeToString(b), 446 } 447 files := []www.File{f} 448 sig, err := signedMerkleRoot(files, nil, cfg.Identity) 449 if err != nil { 450 return nil, err 451 } 452 nd := cms.NewDCC{ 453 File: f, 454 PublicKey: hex.EncodeToString(cfg.Identity.Public.Key[:]), 455 Signature: sig, 456 } 457 ndr, err := client.NewDCC(nd) 458 if err != nil { 459 return nil, fmt.Errorf("NewDCC: %v", err) 460 } 461 462 // Get the full DCC record to return 463 dcc, err := dcc(ndr.CensorshipRecord.Token) 464 if err != nil { 465 return nil, fmt.Errorf("dcc: %v", err) 466 } 467 468 // Log the user out 469 err = logout() 470 if err != nil { 471 return nil, fmt.Errorf("logout: %v", err) 472 } 473 474 return dcc, nil 475 } 476 477 // dcc returns the DCCRecord for the given token. 478 func dcc(token string) (*cms.DCCRecord, error) { 479 ddr, err := client.DCCDetails(token) 480 if err != nil { 481 return nil, err 482 } 483 return &ddr.DCC, nil 484 } 485 486 // dccSetStatus updates the status of the given DCC. 487 // 488 // This function returns with the admin logged out. 489 func dccSetStatus(admin user, token string, st cms.DCCStatusT, reason string) error { 490 err := login(admin) 491 if err != nil { 492 return fmt.Errorf("login: %v", err) 493 } 494 495 sds := SetDCCStatusCmd{} 496 sds.Args.Token = token 497 sds.Args.Status = strconv.Itoa(int(st)) 498 sds.Args.Reason = reason 499 err = sds.Execute(nil) 500 if err != nil { 501 return fmt.Errorf("SetDCCStatusCmd: %v", err) 502 } 503 504 err = logout() 505 if err != nil { 506 return fmt.Errorf("logout: %v", err) 507 } 508 return nil 509 } 510 511 // dccSupport casts a support or oppose vote with the provided user for the 512 // provided dcc. 513 // 514 // This function returns with the user logged out. 515 func dccSupport(u user, token, vote string) error { 516 err := login(u) 517 if err != nil { 518 return fmt.Errorf("login: %v", err) 519 } 520 521 c := SupportOpposeDCCCmd{} 522 c.Args.Token = token 523 c.Args.Vote = vote 524 err = c.Execute(nil) 525 if err != nil { 526 return fmt.Errorf("SupportOpposeDCCCmd: %v", err) 527 } 528 529 err = logout() 530 if err != nil { 531 return fmt.Errorf("logout: %v", err) 532 } 533 534 return nil 535 } 536 537 // dccStartVote starts an all contractor vote for the provided DCC. 538 // 539 // This function returns with the admin logged out. 540 func dccStartVote(admin user, token string) error { 541 err := login(admin) 542 if err != nil { 543 return fmt.Errorf("login: %v", err) 544 } 545 546 svc := StartVoteCmd{} 547 svc.Args.Token = token 548 err = svc.Execute(nil) 549 if err != nil { 550 return fmt.Errorf("StartVoteCmd: %v", err) 551 } 552 553 err = logout() 554 if err != nil { 555 return fmt.Errorf("logout: %v", err) 556 } 557 558 return nil 559 } 560 561 // dccVote casts a support or oppose vote with the provided user for the 562 // provided dcc all contractor vote. 563 // 564 // This function returns with the user logged out. 565 func dccVote(u user, token, vote string) error { 566 err := login(u) 567 if err != nil { 568 return fmt.Errorf("login: %v", err) 569 } 570 571 c := VoteDCCCmd{} 572 c.Args.Token = token 573 c.Args.Vote = vote 574 err = c.Execute(nil) 575 if err != nil { 576 return fmt.Errorf("VoteDCCCmd: %v", err) 577 } 578 579 err = logout() 580 if err != nil { 581 return fmt.Errorf("logout: %v", err) 582 } 583 584 return nil 585 } 586 587 // dccVoteSummary returns the DCC vote summary for the provided DCC token. 588 // 589 // This function returns with the user logged out. 590 func dccVoteSummary(u user, token string) (*cms.VoteSummary, error) { 591 err := login(u) 592 if err != nil { 593 return nil, fmt.Errorf("login: %v", err) 594 } 595 596 ddr, err := client.DCCDetails(token) 597 if err != nil { 598 return nil, fmt.Errorf("DCCDetails: %v", err) 599 } 600 601 err = logout() 602 if err != nil { 603 return nil, fmt.Errorf("logout: %v", err) 604 } 605 606 return &ddr.VoteSummary, nil 607 } 608 609 // dccCommentNew posts a new comment to the provided DCC. 610 // 611 // This function returns with the user logged out. 612 func dccCommentNew(u user, token, parentID, comment string) error { 613 if parentID == "" { 614 parentID = "0" 615 } 616 617 err := login(u) 618 if err != nil { 619 return fmt.Errorf("login: %v", err) 620 } 621 622 c := NewDCCCommentCmd{} 623 c.Args.Token = token 624 c.Args.ParentID = parentID 625 c.Args.Comment = comment 626 err = c.Execute(nil) 627 if err != nil { 628 return fmt.Errorf("NewDCCCommentCmd: %v", err) 629 } 630 631 err = logout() 632 if err != nil { 633 return fmt.Errorf("logout: %v", err) 634 } 635 636 return nil 637 } 638 639 // testDCC tests the DCC (Decred Contractor Clearance) routes. See the proposal 640 // below for more information about the DCC process. 641 // 642 // https://proposals.decred.org/proposals/fa38a35 643 // 644 // A new cms user must have their contractor type updated before they are able 645 // to submit invoices. There are currently two ways for this to happen. 646 // 1. An admin can update it manually using the CMSManageUser route. 647 // 2. The contractor can undergo the DCC process. The DCC process is where 648 // the new contractor is nominated by an existing contractor then other 649 // existing contractors can support or oppose the DCC nomination. Admins 650 // currently have final say in the approval of a DCC. If a DCC is 651 // contentious, it can be put up for an all contractor vote where existing 652 // contractor votes are weighted by the amount of hours they've billed. 653 // Once a DCC has been approved, the user's ContractorType is automatically 654 // updated and they are able to submit invoices. 655 // 656 // testDCC runs through the full DCC process with the exception of the all 657 // contractor vote for contentious DCCs. See testDCCVote() for details on the 658 // all contractor vote. 659 func testDCC(admin user) error { 660 fmt.Printf("Running testDCC\n") 661 662 // Create three users and make them existing contractors 663 fmt.Printf(" create existing contractors\n") 664 665 c1, err := contractorNew(admin, cms.DomainTypeDeveloper, 666 cms.ContractorTypeDirect) 667 if err != nil { 668 return err 669 } 670 c2, err := contractorNew(admin, cms.DomainTypeDeveloper, 671 cms.ContractorTypeDirect) 672 if err != nil { 673 return err 674 } 675 c3, err := contractorNew(admin, cms.DomainTypeDeveloper, 676 cms.ContractorTypeDirect) 677 if err != nil { 678 return err 679 } 680 681 // Create a nominee 682 fmt.Printf(" create a user to be the nominee\n") 683 684 n1, err := userNew(admin) 685 if err != nil { 686 return err 687 } 688 689 // Create a new DCC for the nominee 690 fmt.Printf(" create a DCC for the nominee: ") 691 692 dcc, err := dccNew(*c1, n1.ID, cms.DCCTypeIssuance, 693 cms.DomainTypeDeveloper, cms.ContractorTypeDirect) 694 if err != nil { 695 return err 696 } 697 dccToken := dcc.CensorshipRecord.Token 698 699 fmt.Printf("%v\n", dccToken) 700 701 // Comment on the DCC 702 fmt.Printf(" comment on the DCC\n") 703 704 err = dccCommentNew(*c1, dccToken, "", randomString()) 705 if err != nil { 706 return err 707 } 708 err = dccCommentNew(*c2, dccToken, "", randomString()) 709 if err != nil { 710 return err 711 } 712 err = dccCommentNew(*c3, dccToken, "", randomString()) 713 if err != nil { 714 return err 715 } 716 717 // Support the DCC 718 fmt.Printf(" support the DCC\n") 719 720 err = dccSupport(*c2, dccToken, dccIssuanceSupport) 721 if err != nil { 722 return err 723 } 724 err = dccSupport(*c3, dccToken, dccIssuanceSupport) 725 if err != nil { 726 return err 727 } 728 729 // Have admin approve the DCC 730 fmt.Printf(" approve the DCC\n") 731 732 err = dccSetStatus(admin, dccToken, cms.DCCStatusApproved, 733 "there was non-contentious support") 734 if err != nil { 735 return err 736 } 737 738 // Create a nominee. This time we'll reject their DCC. 739 fmt.Printf(" create a user to be the nominee\n") 740 741 n2, err := userNew(admin) 742 if err != nil { 743 return err 744 } 745 746 // Create a new DCC for the nominee 747 fmt.Printf(" create a DCC for the nominee: ") 748 749 dcc, err = dccNew(*c1, n2.ID, cms.DCCTypeIssuance, 750 cms.DomainTypeDeveloper, cms.ContractorTypeDirect) 751 if err != nil { 752 return err 753 } 754 dccToken = dcc.CensorshipRecord.Token 755 756 fmt.Printf("%v\n", dccToken) 757 758 // Oppose the DCC 759 fmt.Printf(" oppose the DCC\n") 760 761 err = dccSupport(*c2, dccToken, dccIssuanceOppose) 762 if err != nil { 763 return err 764 } 765 err = dccSupport(*c3, dccToken, dccIssuanceOppose) 766 if err != nil { 767 return err 768 } 769 770 // Have admin reject the DCC 771 fmt.Printf(" reject the DCC\n") 772 773 err = dccSetStatus(admin, dccToken, cms.DCCStatusRejected, 774 "there was non-contentious opposition") 775 if err != nil { 776 return err 777 } 778 779 fmt.Printf("testDCC success!\n") 780 781 return nil 782 } 783 784 // testDCCVote tests the DCC all contractor vote routes. 785 // 786 // When a DCC proposal is deemed contentious by the admin, the admin can start 787 // an all contractor vote for the DCC. Contractors are able to cast votes 788 // proportional to the amount of time they've billed, and that has been paid, 789 // over the previous 6 months. Admins still currently have final say over 790 // whether to approve or reject the DCC. 791 // 792 // The admin starts an all contractor vote on a DCC by setting the DCC status 793 // to DCCStatusAllVote. 794 func testDCCVote(admin user) error { 795 fmt.Printf("Running testDCCVote\n") 796 797 // Create three users and make them existing contractors 798 fmt.Printf(" create existing contractors\n") 799 800 c1, err := contractorNew(admin, cms.DomainTypeDeveloper, 801 cms.ContractorTypeDirect) 802 if err != nil { 803 return err 804 } 805 c2, err := contractorNew(admin, cms.DomainTypeDeveloper, 806 cms.ContractorTypeDirect) 807 if err != nil { 808 return err 809 } 810 c3, err := contractorNew(admin, cms.DomainTypeDeveloper, 811 cms.ContractorTypeDirect) 812 if err != nil { 813 return err 814 } 815 816 // Submit and pay invoices for the users so that we can make sure 817 // the vote weights are being calculated correctly. 818 fmt.Printf(" submitting invoices for contractors\n") 819 820 cr1, err := invoiceNew(*c1, 40, 50) 821 if err != nil { 822 return err 823 } 824 cr2, err := invoiceNew(*c2, 40, 100) 825 if err != nil { 826 return err 827 } 828 cr3, err := invoiceNew(*c3, 40, 150) 829 if err != nil { 830 return err 831 } 832 833 fmt.Printf(" marking invoices as approved and paid\n") 834 835 err = invoiceSetStatus(admin, cr1.Token, cms.InvoiceStatusApproved) 836 if err != nil { 837 return err 838 } 839 err = invoiceSetStatus(admin, cr2.Token, cms.InvoiceStatusApproved) 840 if err != nil { 841 return err 842 } 843 err = invoiceSetStatus(admin, cr3.Token, cms.InvoiceStatusApproved) 844 if err != nil { 845 return err 846 } 847 err = invoicesPay(admin) 848 if err != nil { 849 return err 850 } 851 852 // Create a nominee 853 fmt.Printf(" create a user to be the nominee\n") 854 855 n1, err := userNew(admin) 856 if err != nil { 857 return err 858 } 859 860 // Create a new DCC for the nominee 861 fmt.Printf(" create a DCC for the nominee: ") 862 863 dcc, err := dccNew(*c1, n1.ID, cms.DCCTypeIssuance, 864 cms.DomainTypeDeveloper, cms.ContractorTypeDirect) 865 if err != nil { 866 return err 867 } 868 dccToken := dcc.CensorshipRecord.Token 869 870 fmt.Printf("%v\n", dccToken) 871 872 // Support/oppose the DCC 873 fmt.Printf(" support/oppose DCC\n") 874 875 err = dccSupport(*c2, dccToken, dccIssuanceSupport) 876 if err != nil { 877 return err 878 } 879 err = dccSupport(*c3, dccToken, dccIssuanceOppose) 880 if err != nil { 881 return err 882 } 883 884 // Start an all contractor vote for the DCC 885 fmt.Printf(" start DCC vote\n") 886 err = dccStartVote(admin, dccToken) 887 if err != nil { 888 return err 889 } 890 891 // Vote on the DCC 892 fmt.Printf(" cast DCC votes\n") 893 err = dccVote(*c2, dccToken, cmsplugin.DCCApprovalString) 894 if err != nil { 895 return err 896 } 897 expectedApprovalVotes := uint64(1) 898 err = dccVote(*c3, dccToken, cmsplugin.DCCDisapprovalString) 899 if err != nil { 900 return err 901 } 902 expectedDisapprovalVotes := uint64(1) 903 // Check to see that the votes are properly cast in gitbe 904 fmt.Printf(" check DCC votes") 905 vs, err := dccVoteSummary(*c2, dccToken) 906 if err != nil { 907 return err 908 } 909 expectedVoteOptionResults := 2 910 if len(vs.Results) != expectedVoteOptionResults { 911 return fmt.Errorf("unexpected number of vote option results: got %v,"+ 912 " wanted %v", len(vs.Results), expectedVoteOptionResults) 913 } 914 for _, result := range vs.Results { 915 if result.Option.Id == cmsplugin.DCCApprovalString && 916 result.VotesReceived != expectedApprovalVotes { 917 return fmt.Errorf("unexpected amount of %v votes, got %v wanted %v", 918 cmsplugin.DCCApprovalString, result.VotesReceived, 919 expectedApprovalVotes) 920 } 921 if result.Option.Id == cmsplugin.DCCDisapprovalString && 922 result.VotesReceived != expectedDisapprovalVotes { 923 return fmt.Errorf("unexpected amount of %v votes, got %v wanted %v", 924 cmsplugin.DCCDisapprovalString, result.VotesReceived, 925 expectedDisapprovalVotes) 926 } 927 } 928 /* 929 // Have the admin approve the DCC 930 // This can't be done in the test run because the vote needs to 931 // have ended before the admin can change the status. This is how 932 // a DCC is ultimately decided as approved or rejected though. 933 err = dccSetStatus(admin, dccToken, cms.DCCStatusApproved, 934 "I decree it to be") 935 if err != nil { 936 return err 937 } 938 */ 939 940 fmt.Printf("testDCCVote success!\n") 941 942 return nil 943 } 944 945 // Execute executes the TestRun command. 946 func (cmd *TestRunCmd) Execute(args []string) error { 947 const ( 948 modeCMSWWW = "cmswww" 949 ) 950 951 // Suppress output from cli commands 952 cfg.Silent = true 953 954 fmt.Printf("Running pre-testrun validation\n") 955 956 // Validate politeiawww setup 957 vr, err := client.Version() 958 switch { 959 case err != nil: 960 return fmt.Errorf("version: %v", err) 961 case vr.Mode != modeCMSWWW: 962 return fmt.Errorf("politeiawww is not in cmswww mode") 963 case !vr.TestNet: 964 return fmt.Errorf("politeiawww is not on testnet") 965 } 966 967 // Validate admin credentials 968 admin := user{ 969 Email: cmd.Args.AdminEmail, 970 Password: cmd.Args.AdminPassword, 971 } 972 err = login(admin) 973 if err != nil { 974 return err 975 } 976 lr, err := client.Me() 977 if err != nil { 978 return err 979 } 980 if !lr.IsAdmin { 981 return fmt.Errorf("%v is not an admin", admin.Email) 982 } 983 admin.Username = lr.Username 984 985 // Test runs 986 err = testDCC(admin) 987 if err != nil { 988 return fmt.Errorf("testDCC: %v", err) 989 } 990 err = testDCCVote(admin) 991 if err != nil { 992 return fmt.Errorf("testDCCVote: %v", err) 993 } 994 995 fmt.Printf("testrun complete!\n") 996 997 return nil 998 }