// Copyright (c) 2020 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package database import ( "reflect" "testing" "time" ) func exampleTicket() Ticket { return Ticket{ Hash: "Hash", CommitmentAddress: "Address", FeeAddressIndex: 12345, FeeAddress: "FeeAddress", FeeAmount: 10000000, FeeExpiration: 4, Confirmed: false, VoteChoices: map[string]string{"AgendaID": "Choice"}, VotingWIF: "VotingKey", FeeTxHex: "FeeTransction", FeeTxHash: "", FeeTxStatus: FeeBroadcast, } } func testInsertNewTicket(t *testing.T) { // Insert a ticket into the database. ticket := exampleTicket() err := db.InsertNewTicket(ticket) if err != nil { t.Fatalf("error storing ticket in database: %v", err) } // Inserting a ticket with different fee address but same hash should fail. ticket2 := exampleTicket() ticket2.FeeAddress = ticket.FeeAddress + "2" err = db.InsertNewTicket(ticket) if err == nil { t.Fatal("expected an error inserting ticket with duplicate hash") } // Inserting a ticket with different hash but same fee address should fail. ticket3 := exampleTicket() ticket3.FeeAddress = ticket.Hash + "2" err = db.InsertNewTicket(ticket) if err == nil { t.Fatal("expected an error inserting ticket with duplicate fee addr") } // Inserting a ticket with empty hash should fail. ticket.Hash = "" err = db.InsertNewTicket(ticket) if err == nil { t.Fatal("expected an error inserting ticket with no hash") } } func testDeleteTicket(t *testing.T) { // Insert a ticket into the database. ticket := exampleTicket() err := db.InsertNewTicket(ticket) if err != nil { t.Fatalf("error storing ticket in database: %v", err) } // Delete ticket err = db.DeleteTicket(ticket) if err != nil { t.Fatalf("error deleting ticket: %v", err) } // Nothing should be in the db. _, found, err := db.GetTicketByHash(ticket.Hash) if err != nil { t.Fatalf("error retrieving ticket by ticket hash: %v", err) } if found { t.Fatal("expected found==false") } } func testGetTicketByHash(t *testing.T) { ticket := exampleTicket() // Insert a ticket into the database. err := db.InsertNewTicket(ticket) if err != nil { t.Fatalf("error storing ticket in database: %v", err) } // Retrieve ticket from database. retrieved, found, err := db.GetTicketByHash(ticket.Hash) if err != nil { t.Fatalf("error retrieving ticket by ticket hash: %v", err) } if !found { t.Fatal("expected found==true") } // Check ticket fields match expected. if retrieved.Hash != ticket.Hash || retrieved.CommitmentAddress != ticket.CommitmentAddress || retrieved.FeeAddressIndex != ticket.FeeAddressIndex || retrieved.FeeAddress != ticket.FeeAddress || retrieved.FeeAmount != ticket.FeeAmount || retrieved.FeeExpiration != ticket.FeeExpiration || retrieved.Confirmed != ticket.Confirmed || !reflect.DeepEqual(retrieved.VoteChoices, ticket.VoteChoices) || retrieved.VotingWIF != ticket.VotingWIF || retrieved.FeeTxHex != ticket.FeeTxHex || retrieved.FeeTxHash != ticket.FeeTxHash || retrieved.FeeTxStatus != ticket.FeeTxStatus { t.Fatal("retrieved ticket value didnt match expected") } // Check found==false when requesting a non-existent ticket. _, found, err = db.GetTicketByHash("Not a real ticket hash") if err != nil { t.Fatalf("error retrieving ticket by ticket hash: %v", err) } if found { t.Fatal("expected found==false") } } func testUpdateTicket(t *testing.T) { ticket := exampleTicket() // Insert a ticket into the database. err := db.InsertNewTicket(ticket) if err != nil { t.Fatalf("error storing ticket in database: %v", err) } // Update ticket with new values ticket.FeeAmount = ticket.FeeAmount + 1 ticket.FeeExpiration = ticket.FeeExpiration + 1 err = db.UpdateTicket(ticket) if err != nil { t.Fatalf("error updating ticket: %v", err) } // Retrieve ticket from database. retrieved, found, err := db.GetTicketByHash(ticket.Hash) if err != nil { t.Fatalf("error retrieving ticket by ticket hash: %v", err) } if !found { t.Fatal("expected found==true") } if ticket.FeeAmount != retrieved.FeeAmount || ticket.FeeExpiration != retrieved.FeeExpiration { t.Fatal("retrieved ticket value didnt match expected") } // Updating a non-existent ticket should fail. ticket.Hash = "doesnt exist" err = db.UpdateTicket(ticket) if err == nil { t.Fatal("expected an error updating a ticket with non-existent hash") } } func testTicketFeeExpired(t *testing.T) { ticket := exampleTicket() now := time.Now() hourBefore := now.Add(-time.Hour).Unix() hourAfter := now.Add(time.Hour).Unix() ticket.FeeExpiration = hourAfter if ticket.FeeExpired() { t.Fatal("expected ticket not to be expired") } ticket.FeeExpiration = hourBefore if !ticket.FeeExpired() { t.Fatal("expected ticket to be expired") } } func testFilterTickets(t *testing.T) { // Insert a ticket ticket := exampleTicket() err := db.InsertNewTicket(ticket) if err != nil { t.Fatalf("error storing ticket in database: %v", err) } // Insert another ticket ticket.Hash = ticket.Hash + "1" ticket.FeeAddress = ticket.FeeAddress + "1" ticket.FeeAddressIndex = ticket.FeeAddressIndex + 1 ticket.Confirmed = !ticket.Confirmed err = db.InsertNewTicket(ticket) if err != nil { t.Fatalf("error storing ticket in database: %v", err) } // Expect all tickets returned. retrieved, err := db.filterTickets(func(t Ticket) bool { return true }) if err != nil { t.Fatalf("error filtering tickets: %v", err) } if len(retrieved) != 2 { t.Fatal("expected to find 2 tickets") } // Only one ticket should be confirmed. retrieved, err = db.filterTickets(func(t Ticket) bool { return t.Confirmed }) if err != nil { t.Fatalf("error filtering tickets: %v", err) } if len(retrieved) != 1 { t.Fatal("expected to find 2 tickets") } if retrieved[0].Confirmed != true { t.Fatal("expected retrieved ticket to be confirmed") } // Expect no tickets with confirmed fee. retrieved, err = db.filterTickets(func(t Ticket) bool { return t.FeeTxStatus == FeeConfirmed }) if err != nil { t.Fatalf("error filtering tickets: %v", err) } if len(retrieved) != 0 { t.Fatal("expected to find 0 tickets") } } func testCountTickets(t *testing.T) { count := func(test string, expectedVoting, expectedVoted, expectedRevoked int64) { voting, voted, revoked, err := db.CountTickets() if err != nil { t.Fatalf("error counting tickets: %v", err) } if voting != expectedVoting { t.Fatalf("test %s: expected %d voting tickets, got %d", test, expectedVoting, voting) } if voted != expectedVoted { t.Fatalf("test %s: expected %d voted tickets, got %d", test, expectedVoted, voted) } if revoked != expectedRevoked { t.Fatalf("test %s: expected %d revoked tickets, got %d", test, expectedRevoked, revoked) } } // Initial counts should all be zero. count("empty db", 0, 0, 0) // Insert a ticket with non-confirmed fee into the database. // This should not be counted. ticket := exampleTicket() ticket.FeeTxStatus = FeeReceieved err := db.InsertNewTicket(ticket) if err != nil { t.Fatalf("error storing ticket in database: %v", err) } count("unconfirmed fee", 0, 0, 0) // Insert a ticket with confirmed fee into the database. // This should be counted. ticket.Hash = ticket.Hash + "1" ticket.FeeAddress = ticket.FeeAddress + "1" ticket.FeeAddressIndex = ticket.FeeAddressIndex + 1 ticket.FeeTxStatus = FeeConfirmed err = db.InsertNewTicket(ticket) if err != nil { t.Fatalf("error storing ticket in database: %v", err) } count("confirmed fee", 1, 0, 0) // Insert a voted ticket into the database. // This should be counted. ticket.Hash = ticket.Hash + "1" ticket.FeeAddress = ticket.FeeAddress + "1" ticket.FeeAddressIndex = ticket.FeeAddressIndex + 1 ticket.FeeTxStatus = FeeConfirmed ticket.Outcome = Voted err = db.InsertNewTicket(ticket) if err != nil { t.Fatalf("error storing ticket in database: %v", err) } count("voted", 1, 1, 0) // Insert a revoked ticket into the database. ticket.Hash = ticket.Hash + "1" ticket.FeeAddress = ticket.FeeAddress + "1" ticket.FeeAddressIndex = ticket.FeeAddressIndex + 1 ticket.FeeTxStatus = FeeConfirmed ticket.Outcome = Revoked err = db.InsertNewTicket(ticket) if err != nil { t.Fatalf("error storing ticket in database: %v", err) } count("revoked", 1, 1, 1) }