mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-23 01:55:10 +00:00
Add Tests to FabCar Go Chaincode
Many users raise questions on how to test chaincode. It used to be much easier with the old shim, as they could directly use the old mock stub in the shim. Now that it no longer exists the fabcar example can provide an example of how to test chaincode. Also worth noting is our current directory structure of our Go chaincodes prevents the creation of mocks due to import cycles. This change also pushes the chaincode logic down into a `contract` package. Signed-off-by: Brett Logan <brett.t.logan@ibm.com>
This commit is contained in:
parent
683ee8b347
commit
23dc4e698a
8 changed files with 3574 additions and 124 deletions
2881
chaincode/fabcar/go/contract/mocks/chaincodestub.go
Normal file
2881
chaincode/fabcar/go/contract/mocks/chaincodestub.go
Normal file
File diff suppressed because it is too large
Load diff
235
chaincode/fabcar/go/contract/mocks/statequeryiterator.go
Normal file
235
chaincode/fabcar/go/contract/mocks/statequeryiterator.go
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hyperledger/fabric-protos-go/ledger/queryresult"
|
||||
"github.com/hyperledger/fabric-samples/chaincode/fabcar/go/contract"
|
||||
)
|
||||
|
||||
type StateQueryIterator struct {
|
||||
CloseStub func() error
|
||||
closeMutex sync.RWMutex
|
||||
closeArgsForCall []struct {
|
||||
}
|
||||
closeReturns struct {
|
||||
result1 error
|
||||
}
|
||||
closeReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
HasNextStub func() bool
|
||||
hasNextMutex sync.RWMutex
|
||||
hasNextArgsForCall []struct {
|
||||
}
|
||||
hasNextReturns struct {
|
||||
result1 bool
|
||||
}
|
||||
hasNextReturnsOnCall map[int]struct {
|
||||
result1 bool
|
||||
}
|
||||
NextStub func() (*queryresult.KV, error)
|
||||
nextMutex sync.RWMutex
|
||||
nextArgsForCall []struct {
|
||||
}
|
||||
nextReturns struct {
|
||||
result1 *queryresult.KV
|
||||
result2 error
|
||||
}
|
||||
nextReturnsOnCall map[int]struct {
|
||||
result1 *queryresult.KV
|
||||
result2 error
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) Close() error {
|
||||
fake.closeMutex.Lock()
|
||||
ret, specificReturn := fake.closeReturnsOnCall[len(fake.closeArgsForCall)]
|
||||
fake.closeArgsForCall = append(fake.closeArgsForCall, struct {
|
||||
}{})
|
||||
fake.recordInvocation("Close", []interface{}{})
|
||||
fake.closeMutex.Unlock()
|
||||
if fake.CloseStub != nil {
|
||||
return fake.CloseStub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
fakeReturns := fake.closeReturns
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) CloseCallCount() int {
|
||||
fake.closeMutex.RLock()
|
||||
defer fake.closeMutex.RUnlock()
|
||||
return len(fake.closeArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) CloseCalls(stub func() error) {
|
||||
fake.closeMutex.Lock()
|
||||
defer fake.closeMutex.Unlock()
|
||||
fake.CloseStub = stub
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) CloseReturns(result1 error) {
|
||||
fake.closeMutex.Lock()
|
||||
defer fake.closeMutex.Unlock()
|
||||
fake.CloseStub = nil
|
||||
fake.closeReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) CloseReturnsOnCall(i int, result1 error) {
|
||||
fake.closeMutex.Lock()
|
||||
defer fake.closeMutex.Unlock()
|
||||
fake.CloseStub = nil
|
||||
if fake.closeReturnsOnCall == nil {
|
||||
fake.closeReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.closeReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) HasNext() bool {
|
||||
fake.hasNextMutex.Lock()
|
||||
ret, specificReturn := fake.hasNextReturnsOnCall[len(fake.hasNextArgsForCall)]
|
||||
fake.hasNextArgsForCall = append(fake.hasNextArgsForCall, struct {
|
||||
}{})
|
||||
fake.recordInvocation("HasNext", []interface{}{})
|
||||
fake.hasNextMutex.Unlock()
|
||||
if fake.HasNextStub != nil {
|
||||
return fake.HasNextStub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
fakeReturns := fake.hasNextReturns
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) HasNextCallCount() int {
|
||||
fake.hasNextMutex.RLock()
|
||||
defer fake.hasNextMutex.RUnlock()
|
||||
return len(fake.hasNextArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) HasNextCalls(stub func() bool) {
|
||||
fake.hasNextMutex.Lock()
|
||||
defer fake.hasNextMutex.Unlock()
|
||||
fake.HasNextStub = stub
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) HasNextReturns(result1 bool) {
|
||||
fake.hasNextMutex.Lock()
|
||||
defer fake.hasNextMutex.Unlock()
|
||||
fake.HasNextStub = nil
|
||||
fake.hasNextReturns = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) HasNextReturnsOnCall(i int, result1 bool) {
|
||||
fake.hasNextMutex.Lock()
|
||||
defer fake.hasNextMutex.Unlock()
|
||||
fake.HasNextStub = nil
|
||||
if fake.hasNextReturnsOnCall == nil {
|
||||
fake.hasNextReturnsOnCall = make(map[int]struct {
|
||||
result1 bool
|
||||
})
|
||||
}
|
||||
fake.hasNextReturnsOnCall[i] = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) Next() (*queryresult.KV, error) {
|
||||
fake.nextMutex.Lock()
|
||||
ret, specificReturn := fake.nextReturnsOnCall[len(fake.nextArgsForCall)]
|
||||
fake.nextArgsForCall = append(fake.nextArgsForCall, struct {
|
||||
}{})
|
||||
fake.recordInvocation("Next", []interface{}{})
|
||||
fake.nextMutex.Unlock()
|
||||
if fake.NextStub != nil {
|
||||
return fake.NextStub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
fakeReturns := fake.nextReturns
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) NextCallCount() int {
|
||||
fake.nextMutex.RLock()
|
||||
defer fake.nextMutex.RUnlock()
|
||||
return len(fake.nextArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) NextCalls(stub func() (*queryresult.KV, error)) {
|
||||
fake.nextMutex.Lock()
|
||||
defer fake.nextMutex.Unlock()
|
||||
fake.NextStub = stub
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) NextReturns(result1 *queryresult.KV, result2 error) {
|
||||
fake.nextMutex.Lock()
|
||||
defer fake.nextMutex.Unlock()
|
||||
fake.NextStub = nil
|
||||
fake.nextReturns = struct {
|
||||
result1 *queryresult.KV
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) NextReturnsOnCall(i int, result1 *queryresult.KV, result2 error) {
|
||||
fake.nextMutex.Lock()
|
||||
defer fake.nextMutex.Unlock()
|
||||
fake.NextStub = nil
|
||||
if fake.nextReturnsOnCall == nil {
|
||||
fake.nextReturnsOnCall = make(map[int]struct {
|
||||
result1 *queryresult.KV
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.nextReturnsOnCall[i] = struct {
|
||||
result1 *queryresult.KV
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.closeMutex.RLock()
|
||||
defer fake.closeMutex.RUnlock()
|
||||
fake.hasNextMutex.RLock()
|
||||
defer fake.hasNextMutex.RUnlock()
|
||||
fake.nextMutex.RLock()
|
||||
defer fake.nextMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *StateQueryIterator) recordInvocation(key string, args []interface{}) {
|
||||
fake.invocationsMutex.Lock()
|
||||
defer fake.invocationsMutex.Unlock()
|
||||
if fake.invocations == nil {
|
||||
fake.invocations = map[string][][]interface{}{}
|
||||
}
|
||||
if fake.invocations[key] == nil {
|
||||
fake.invocations[key] = [][]interface{}{}
|
||||
}
|
||||
fake.invocations[key] = append(fake.invocations[key], args)
|
||||
}
|
||||
|
||||
var _ contract.StateQueryIterator = new(StateQueryIterator)
|
||||
167
chaincode/fabcar/go/contract/mocks/transaction.go
Normal file
167
chaincode/fabcar/go/contract/mocks/transaction.go
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
// Code generated by counterfeiter. DO NOT EDIT.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/pkg/cid"
|
||||
"github.com/hyperledger/fabric-chaincode-go/shim"
|
||||
"github.com/hyperledger/fabric-samples/chaincode/fabcar/go/contract"
|
||||
)
|
||||
|
||||
type TransactionContext struct {
|
||||
GetClientIdentityStub func() cid.ClientIdentity
|
||||
getClientIdentityMutex sync.RWMutex
|
||||
getClientIdentityArgsForCall []struct {
|
||||
}
|
||||
getClientIdentityReturns struct {
|
||||
result1 cid.ClientIdentity
|
||||
}
|
||||
getClientIdentityReturnsOnCall map[int]struct {
|
||||
result1 cid.ClientIdentity
|
||||
}
|
||||
GetStubStub func() shim.ChaincodeStubInterface
|
||||
getStubMutex sync.RWMutex
|
||||
getStubArgsForCall []struct {
|
||||
}
|
||||
getStubReturns struct {
|
||||
result1 shim.ChaincodeStubInterface
|
||||
}
|
||||
getStubReturnsOnCall map[int]struct {
|
||||
result1 shim.ChaincodeStubInterface
|
||||
}
|
||||
invocations map[string][][]interface{}
|
||||
invocationsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetClientIdentity() cid.ClientIdentity {
|
||||
fake.getClientIdentityMutex.Lock()
|
||||
ret, specificReturn := fake.getClientIdentityReturnsOnCall[len(fake.getClientIdentityArgsForCall)]
|
||||
fake.getClientIdentityArgsForCall = append(fake.getClientIdentityArgsForCall, struct {
|
||||
}{})
|
||||
fake.recordInvocation("GetClientIdentity", []interface{}{})
|
||||
fake.getClientIdentityMutex.Unlock()
|
||||
if fake.GetClientIdentityStub != nil {
|
||||
return fake.GetClientIdentityStub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
fakeReturns := fake.getClientIdentityReturns
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetClientIdentityCallCount() int {
|
||||
fake.getClientIdentityMutex.RLock()
|
||||
defer fake.getClientIdentityMutex.RUnlock()
|
||||
return len(fake.getClientIdentityArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetClientIdentityCalls(stub func() cid.ClientIdentity) {
|
||||
fake.getClientIdentityMutex.Lock()
|
||||
defer fake.getClientIdentityMutex.Unlock()
|
||||
fake.GetClientIdentityStub = stub
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetClientIdentityReturns(result1 cid.ClientIdentity) {
|
||||
fake.getClientIdentityMutex.Lock()
|
||||
defer fake.getClientIdentityMutex.Unlock()
|
||||
fake.GetClientIdentityStub = nil
|
||||
fake.getClientIdentityReturns = struct {
|
||||
result1 cid.ClientIdentity
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetClientIdentityReturnsOnCall(i int, result1 cid.ClientIdentity) {
|
||||
fake.getClientIdentityMutex.Lock()
|
||||
defer fake.getClientIdentityMutex.Unlock()
|
||||
fake.GetClientIdentityStub = nil
|
||||
if fake.getClientIdentityReturnsOnCall == nil {
|
||||
fake.getClientIdentityReturnsOnCall = make(map[int]struct {
|
||||
result1 cid.ClientIdentity
|
||||
})
|
||||
}
|
||||
fake.getClientIdentityReturnsOnCall[i] = struct {
|
||||
result1 cid.ClientIdentity
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetStub() shim.ChaincodeStubInterface {
|
||||
fake.getStubMutex.Lock()
|
||||
ret, specificReturn := fake.getStubReturnsOnCall[len(fake.getStubArgsForCall)]
|
||||
fake.getStubArgsForCall = append(fake.getStubArgsForCall, struct {
|
||||
}{})
|
||||
fake.recordInvocation("GetStub", []interface{}{})
|
||||
fake.getStubMutex.Unlock()
|
||||
if fake.GetStubStub != nil {
|
||||
return fake.GetStubStub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
fakeReturns := fake.getStubReturns
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetStubCallCount() int {
|
||||
fake.getStubMutex.RLock()
|
||||
defer fake.getStubMutex.RUnlock()
|
||||
return len(fake.getStubArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetStubCalls(stub func() shim.ChaincodeStubInterface) {
|
||||
fake.getStubMutex.Lock()
|
||||
defer fake.getStubMutex.Unlock()
|
||||
fake.GetStubStub = stub
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetStubReturns(result1 shim.ChaincodeStubInterface) {
|
||||
fake.getStubMutex.Lock()
|
||||
defer fake.getStubMutex.Unlock()
|
||||
fake.GetStubStub = nil
|
||||
fake.getStubReturns = struct {
|
||||
result1 shim.ChaincodeStubInterface
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) GetStubReturnsOnCall(i int, result1 shim.ChaincodeStubInterface) {
|
||||
fake.getStubMutex.Lock()
|
||||
defer fake.getStubMutex.Unlock()
|
||||
fake.GetStubStub = nil
|
||||
if fake.getStubReturnsOnCall == nil {
|
||||
fake.getStubReturnsOnCall = make(map[int]struct {
|
||||
result1 shim.ChaincodeStubInterface
|
||||
})
|
||||
}
|
||||
fake.getStubReturnsOnCall[i] = struct {
|
||||
result1 shim.ChaincodeStubInterface
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) Invocations() map[string][][]interface{} {
|
||||
fake.invocationsMutex.RLock()
|
||||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.getClientIdentityMutex.RLock()
|
||||
defer fake.getClientIdentityMutex.RUnlock()
|
||||
fake.getStubMutex.RLock()
|
||||
defer fake.getStubMutex.RUnlock()
|
||||
copiedInvocations := map[string][][]interface{}{}
|
||||
for key, value := range fake.invocations {
|
||||
copiedInvocations[key] = value
|
||||
}
|
||||
return copiedInvocations
|
||||
}
|
||||
|
||||
func (fake *TransactionContext) recordInvocation(key string, args []interface{}) {
|
||||
fake.invocationsMutex.Lock()
|
||||
defer fake.invocationsMutex.Unlock()
|
||||
if fake.invocations == nil {
|
||||
fake.invocations = map[string][][]interface{}{}
|
||||
}
|
||||
if fake.invocations[key] == nil {
|
||||
fake.invocations[key] = [][]interface{}{}
|
||||
}
|
||||
fake.invocations[key] = append(fake.invocations[key], args)
|
||||
}
|
||||
|
||||
var _ contract.TransactionContext = new(TransactionContext)
|
||||
21
chaincode/fabcar/go/contract/shim.go
Normal file
21
chaincode/fabcar/go/contract/shim.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package contract
|
||||
|
||||
import (
|
||||
"github.com/hyperledger/fabric-chaincode-go/shim"
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
)
|
||||
|
||||
//go:generate counterfeiter -o mocks/transaction.go -fake-name TransactionContext . TransactionContext
|
||||
type TransactionContext interface {
|
||||
contractapi.TransactionContextInterface
|
||||
}
|
||||
|
||||
//go:generate counterfeiter -o mocks/chaincodestub.go -fake-name ChaincodeStub . ChaincodeStub
|
||||
type ChaincodeStub interface {
|
||||
shim.ChaincodeStubInterface
|
||||
}
|
||||
|
||||
//go:generate counterfeiter -o mocks/statequeryiterator.go -fake-name StateQueryIterator . StateQueryIterator
|
||||
type StateQueryIterator interface {
|
||||
shim.StateQueryIteratorInterface
|
||||
}
|
||||
127
chaincode/fabcar/go/contract/smartcontract.go
Normal file
127
chaincode/fabcar/go/contract/smartcontract.go
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
package contract
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
)
|
||||
|
||||
// SmartContract provides functions for managing a car
|
||||
type SmartContract struct {
|
||||
contractapi.Contract
|
||||
}
|
||||
|
||||
// Car describes basic details of what makes up a car
|
||||
type Car struct {
|
||||
Make string `json:"make"`
|
||||
Model string `json:"model"`
|
||||
Colour string `json:"colour"`
|
||||
Owner string `json:"owner"`
|
||||
}
|
||||
|
||||
// QueryResult structure used for handling result of query
|
||||
type QueryResult struct {
|
||||
Key string `json:"Key"`
|
||||
Record *Car
|
||||
}
|
||||
|
||||
// InitLedger adds a base set of cars to the ledger
|
||||
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
|
||||
cars := []Car{
|
||||
{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
|
||||
{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
|
||||
{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
|
||||
{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
|
||||
{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
|
||||
{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},
|
||||
{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
|
||||
{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
|
||||
{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
|
||||
{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
|
||||
}
|
||||
|
||||
for i, car := range cars {
|
||||
carAsBytes, _ := json.Marshal(car)
|
||||
key := fmt.Sprintf("CAR%d", i)
|
||||
err := ctx.GetStub().PutState(key, carAsBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put to world state. %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateCar adds a new car to the world state with given details
|
||||
func (s *SmartContract) CreateCar(ctx contractapi.TransactionContextInterface, carNumber string, make string, model string, colour string, owner string) error {
|
||||
car := Car{
|
||||
Make: make,
|
||||
Model: model,
|
||||
Colour: colour,
|
||||
Owner: owner,
|
||||
}
|
||||
carAsBytes, err := json.Marshal(car)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed marshalling to json: %v", err)
|
||||
}
|
||||
return ctx.GetStub().PutState(carNumber, carAsBytes)
|
||||
}
|
||||
|
||||
// QueryCar returns the car stored in the world state with given id
|
||||
func (s *SmartContract) QueryCar(ctx contractapi.TransactionContextInterface, carNumber string) (*Car, error) {
|
||||
carAsBytes, err := ctx.GetStub().GetState(carNumber)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read from world state: %v", err)
|
||||
}
|
||||
if carAsBytes == nil {
|
||||
return nil, fmt.Errorf("%s does not exist", carNumber)
|
||||
}
|
||||
var car Car
|
||||
err = json.Unmarshal(carAsBytes, &car)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &car, nil
|
||||
}
|
||||
|
||||
// QueryAllCars returns all cars found in world state
|
||||
func (s *SmartContract) QueryAllCars(ctx contractapi.TransactionContextInterface) ([]QueryResult, error) {
|
||||
// Return all cars by using empty startKey and endKey
|
||||
resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resultsIterator.Close()
|
||||
|
||||
var results []QueryResult
|
||||
for resultsIterator.HasNext() {
|
||||
queryResponse, err := resultsIterator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var car *Car
|
||||
err = json.Unmarshal(queryResponse.Value, &car)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed marshalling to json: %v", err)
|
||||
}
|
||||
queryResult := QueryResult{Key: queryResponse.Key, Record: car}
|
||||
results = append(results, queryResult)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// ChangeCarOwner updates the owner field of car with given id in world state
|
||||
func (s *SmartContract) ChangeCarOwner(ctx contractapi.TransactionContextInterface, carNumber string, newOwner string) error {
|
||||
car, err := s.QueryCar(ctx, carNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
car.Owner = newOwner
|
||||
carAsBytes, err := json.Marshal(car)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed marshalling to json: %v", err)
|
||||
}
|
||||
return ctx.GetStub().PutState(carNumber, carAsBytes)
|
||||
}
|
||||
133
chaincode/fabcar/go/contract/smartcontract_test.go
Normal file
133
chaincode/fabcar/go/contract/smartcontract_test.go
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package contract_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hyperledger/fabric-protos-go/ledger/queryresult"
|
||||
"github.com/hyperledger/fabric-samples/chaincode/fabcar/go/contract"
|
||||
"github.com/hyperledger/fabric-samples/chaincode/fabcar/go/contract/mocks"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInitLedger(t *testing.T) {
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
fabcar := &contract.SmartContract{}
|
||||
err := fabcar.InitLedger(transactionContext)
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.PutStateReturns(fmt.Errorf("failed inserting key"))
|
||||
err = fabcar.InitLedger(transactionContext)
|
||||
require.EqualError(t, err, "failed to put to world state. failed inserting key")
|
||||
}
|
||||
|
||||
func TestCreateCar(t *testing.T) {
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
fabcar := &contract.SmartContract{}
|
||||
err := fabcar.CreateCar(transactionContext, "CAR1", "Ford", "F150", "red", "Tim")
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.PutStateReturns(fmt.Errorf("failed inserting key"))
|
||||
err = fabcar.CreateCar(transactionContext, "CAR1", "Ford", "F150", "red", "Tim")
|
||||
require.EqualError(t, err, "failed inserting key")
|
||||
|
||||
}
|
||||
|
||||
func TestQueryCar(t *testing.T) {
|
||||
car := &contract.Car{
|
||||
Make: "Ford",
|
||||
Model: "F150",
|
||||
Colour: "red",
|
||||
Owner: "Tim",
|
||||
}
|
||||
bytes, err := json.Marshal(car)
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
chaincodeStub.GetStateReturns(bytes, nil)
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
fabcar := &contract.SmartContract{}
|
||||
car, err = fabcar.QueryCar(transactionContext, "CAR1")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, car)
|
||||
|
||||
chaincodeStub.GetStateReturns(nil, nil)
|
||||
car, err = fabcar.QueryCar(transactionContext, "CAR1")
|
||||
require.EqualError(t, err, "CAR1 does not exist")
|
||||
require.Nil(t, car)
|
||||
|
||||
chaincodeStub.GetStateReturns([]byte{}, fmt.Errorf("failed retrieving key"))
|
||||
car, err = fabcar.QueryCar(transactionContext, "CAR1")
|
||||
require.Nil(t, car)
|
||||
require.EqualError(t, err, "failed to read from world state: failed retrieving key")
|
||||
}
|
||||
|
||||
func TestQueryAllCars(t *testing.T) {
|
||||
car := &contract.Car{
|
||||
Make: "Ford",
|
||||
Model: "F150",
|
||||
Colour: "red",
|
||||
Owner: "Tim",
|
||||
}
|
||||
bytes, err := json.Marshal(car)
|
||||
require.NoError(t, err)
|
||||
|
||||
iterator := &mocks.StateQueryIterator{}
|
||||
iterator.HasNextReturnsOnCall(0, true)
|
||||
iterator.HasNextReturnsOnCall(1, false)
|
||||
iterator.NextReturns(&queryresult.KV{Value: bytes}, nil)
|
||||
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
chaincodeStub.GetStateByRangeReturns(iterator, nil)
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
fabcar := &contract.SmartContract{}
|
||||
cars, err := fabcar.QueryAllCars(transactionContext)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cars)
|
||||
|
||||
iterator.HasNextReturns(true)
|
||||
iterator.NextReturns(nil, fmt.Errorf("failed retrieving next item"))
|
||||
cars, err = fabcar.QueryAllCars(transactionContext)
|
||||
require.EqualError(t, err, "failed retrieving next item")
|
||||
require.Nil(t, cars)
|
||||
|
||||
chaincodeStub.GetStateByRangeReturns(nil, fmt.Errorf("failed retrieving all cars"))
|
||||
cars, err = fabcar.QueryAllCars(transactionContext)
|
||||
require.EqualError(t, err, "failed retrieving all cars")
|
||||
require.Nil(t, cars)
|
||||
}
|
||||
|
||||
func TestChangeCarOwner(t *testing.T) {
|
||||
car := &contract.Car{
|
||||
Make: "Ford",
|
||||
Model: "F150",
|
||||
Colour: "red",
|
||||
Owner: "Tim",
|
||||
}
|
||||
bytes, err := json.Marshal(car)
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub := &mocks.ChaincodeStub{}
|
||||
chaincodeStub.GetStateReturns(bytes, nil)
|
||||
transactionContext := &mocks.TransactionContext{}
|
||||
transactionContext.GetStubReturns(chaincodeStub)
|
||||
|
||||
fabcar := &contract.SmartContract{}
|
||||
err = fabcar.ChangeCarOwner(transactionContext, "CAR1", "Ben")
|
||||
require.NoError(t, err)
|
||||
|
||||
chaincodeStub.GetStateReturns(nil, nil)
|
||||
err = fabcar.ChangeCarOwner(transactionContext, "CAR1", "Ben")
|
||||
require.EqualError(t, err, "CAR1 does not exist")
|
||||
}
|
||||
|
|
@ -5,135 +5,14 @@ SPDX-License-Identifier: Apache-2.0
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
"github.com/hyperledger/fabric-samples/chaincode/fabcar/go/contract"
|
||||
)
|
||||
|
||||
// SmartContract provides functions for managing a car
|
||||
type SmartContract struct {
|
||||
contractapi.Contract
|
||||
}
|
||||
|
||||
// Car describes basic details of what makes up a car
|
||||
type Car struct {
|
||||
Make string `json:"make"`
|
||||
Model string `json:"model"`
|
||||
Colour string `json:"colour"`
|
||||
Owner string `json:"owner"`
|
||||
}
|
||||
|
||||
// QueryResult structure used for handling result of query
|
||||
type QueryResult struct {
|
||||
Key string `json:"Key"`
|
||||
Record *Car
|
||||
}
|
||||
|
||||
// InitLedger adds a base set of cars to the ledger
|
||||
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
|
||||
cars := []Car{
|
||||
{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
|
||||
{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
|
||||
{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
|
||||
{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
|
||||
{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
|
||||
{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},
|
||||
{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
|
||||
{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
|
||||
{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
|
||||
{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
|
||||
}
|
||||
|
||||
for i, car := range cars {
|
||||
carAsBytes, _ := json.Marshal(car)
|
||||
key := fmt.Sprintf("CAR%d", i)
|
||||
err := ctx.GetStub().PutState(key, carAsBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put to world state. %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateCar adds a new car to the world state with given details
|
||||
func (s *SmartContract) CreateCar(ctx contractapi.TransactionContextInterface, carNumber string, make string, model string, colour string, owner string) error {
|
||||
car := Car{
|
||||
Make: make,
|
||||
Model: model,
|
||||
Colour: colour,
|
||||
Owner: owner,
|
||||
}
|
||||
carAsBytes, err := json.Marshal(car)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed marshalling to json: %v", err)
|
||||
}
|
||||
return ctx.GetStub().PutState(carNumber, carAsBytes)
|
||||
}
|
||||
|
||||
// QueryCar returns the car stored in the world state with given id
|
||||
func (s *SmartContract) QueryCar(ctx contractapi.TransactionContextInterface, carNumber string) (*Car, error) {
|
||||
carAsBytes, err := ctx.GetStub().GetState(carNumber)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read from world state: %v", err)
|
||||
}
|
||||
if carAsBytes == nil {
|
||||
return nil, fmt.Errorf("%s does not exist", carNumber)
|
||||
}
|
||||
var car *Car
|
||||
err = json.Unmarshal(carAsBytes, car)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return car, nil
|
||||
}
|
||||
|
||||
// QueryAllCars returns all cars found in world state
|
||||
func (s *SmartContract) QueryAllCars(ctx contractapi.TransactionContextInterface) ([]QueryResult, error) {
|
||||
// Return all cars by using empty startKey and endKey
|
||||
resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resultsIterator.Close()
|
||||
|
||||
var results []QueryResult
|
||||
for resultsIterator.HasNext() {
|
||||
queryResponse, err := resultsIterator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var car *Car
|
||||
err = json.Unmarshal(queryResponse.Value, car)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed marshalling to json: %v", err)
|
||||
}
|
||||
queryResult := QueryResult{Key: queryResponse.Key, Record: car}
|
||||
results = append(results, queryResult)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// ChangeCarOwner updates the owner field of car with given id in world state
|
||||
func (s *SmartContract) ChangeCarOwner(ctx contractapi.TransactionContextInterface, carNumber string, newOwner string) error {
|
||||
car, err := s.QueryCar(ctx, carNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
car.Owner = newOwner
|
||||
carAsBytes, err := json.Marshal(car)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed marshalling to json: %v", err)
|
||||
}
|
||||
return ctx.GetStub().PutState(carNumber, carAsBytes)
|
||||
}
|
||||
|
||||
func main() {
|
||||
chaincode, err := contractapi.NewChaincode(&SmartContract{})
|
||||
chaincode, err := contractapi.NewChaincode(&contract.SmartContract{})
|
||||
if err != nil {
|
||||
log.Panicf("Error create fabcar chaincode: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,4 +2,11 @@ module github.com/hyperledger/fabric-samples/chaincode/fabcar/go
|
|||
|
||||
go 1.13
|
||||
|
||||
require github.com/hyperledger/fabric-contract-api-go v1.1.0
|
||||
require (
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.0
|
||||
github.com/golang/protobuf v1.3.2
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.0
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e
|
||||
github.com/stretchr/testify v1.5.1
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue