diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 85362e9..3ae827e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,5 +24,8 @@ jobs: - name: Build run: go build -v github.com/hyperledger-labs/cc-tools-demo/chaincode - - name: Test - run: go test github.com/hyperledger-labs/cc-tools-demo/chaincode -coverpkg=./... -v \ No newline at end of file + - name: Unit tests + run: go test github.com/hyperledger-labs/cc-tools-demo/chaincode -coverpkg=./... -v + + - name: Integration tests + run: go test -v ./tests diff --git a/ccapi/chaincode/invoke.go b/ccapi/chaincode/invoke.go index abbc8b9..a83a5c5 100644 --- a/ccapi/chaincode/invoke.go +++ b/ccapi/chaincode/invoke.go @@ -29,10 +29,9 @@ func Invoke(channelName, ccName, txName string, txArgs [][]byte, transientReques } res, err := fabMngr.Client.Execute(rq, channel.WithRetry(retry.DefaultChannelOpts)) - if err != nil { - return nil, http.StatusInternalServerError, err + return nil, extractStatusCode(err.Error()), err } - return &res, http.StatusInternalServerError, nil + return &res, http.StatusOK, nil } diff --git a/ccapi/chaincode/query.go b/ccapi/chaincode/query.go index 42eb8e4..4b5d139 100644 --- a/ccapi/chaincode/query.go +++ b/ccapi/chaincode/query.go @@ -23,10 +23,10 @@ func Query(channelName, ccName, txName string, txArgs [][]byte) (*channel.Respon } res, err := fabMngr.Client.Query(rq, channel.WithRetry(retry.DefaultChannelOpts)) - if err != nil { - return nil, http.StatusInternalServerError, err + status := extractStatusCode(err.Error()) + return nil, status, err } - return &res, http.StatusInternalServerError, nil + return &res, http.StatusOK, nil } diff --git a/ccapi/chaincode/utils.go b/ccapi/chaincode/utils.go new file mode 100644 index 0000000..6132c9c --- /dev/null +++ b/ccapi/chaincode/utils.go @@ -0,0 +1,26 @@ +package chaincode + +import ( + "fmt" + "net/http" + "regexp" + "strconv" +) + +func extractStatusCode(msg string) int { + re := regexp.MustCompile(`Code:\s*\((\d+)\)`) + + matches := re.FindStringSubmatch(msg) + if len(matches) == 0 { + fmt.Println("No status code found in message") + return http.StatusInternalServerError + } + + statusCode, err := strconv.Atoi(matches[1]) + if err != nil { + fmt.Println("Failed to parse string to int when extracting status code") + return http.StatusInternalServerError + } + + return statusCode +} diff --git a/ccapi/handlers/invoke.go b/ccapi/handlers/invoke.go index 11e8f76..4cc0ce9 100644 --- a/ccapi/handlers/invoke.go +++ b/ccapi/handlers/invoke.go @@ -80,7 +80,7 @@ func Invoke(c *gin.Context) { res, status, err := chaincode.Invoke(channelName, chaincodeName, txName, argList, transientMapByte) if err != nil { - common.Abort(c, http.StatusInternalServerError, err) + common.Abort(c, status, err) return } diff --git a/ccapi/handlers/invokeV1.go b/ccapi/handlers/invokeV1.go index 361cedb..14215a0 100644 --- a/ccapi/handlers/invokeV1.go +++ b/ccapi/handlers/invokeV1.go @@ -82,7 +82,7 @@ func InvokeV1(c *gin.Context) { res, status, err := chaincode.Invoke(channelName, chaincodeName, txName, argList, transientMapByte) if err != nil { - common.Abort(c, http.StatusInternalServerError, err) + common.Abort(c, status, err) return } diff --git a/ccapi/handlers/query.go b/ccapi/handlers/query.go index 48aef98..915786d 100644 --- a/ccapi/handlers/query.go +++ b/ccapi/handlers/query.go @@ -40,7 +40,7 @@ func Query(c *gin.Context) { res, status, err := chaincode.Query(channelName, chaincodeName, txName, argList) if err != nil { - common.Abort(c, http.StatusInternalServerError, err) + common.Abort(c, status, err) return } diff --git a/ccapi/handlers/queryV1.go b/ccapi/handlers/queryV1.go index 6d1b758..153b1f9 100644 --- a/ccapi/handlers/queryV1.go +++ b/ccapi/handlers/queryV1.go @@ -41,7 +41,7 @@ func QueryV1(c *gin.Context) { res, status, err := chaincode.Query(channelName, chaincodeName, txName, argList) if err != nil { - common.Abort(c, http.StatusInternalServerError, err) + common.Abort(c, status, err) return } diff --git a/chaincode/tests/features/createNewLibrary.feature b/chaincode/tests/features/createNewLibrary.feature index a6a33a2..5bf5620 100644 --- a/chaincode/tests/features/createNewLibrary.feature +++ b/chaincode/tests/features/createNewLibrary.feature @@ -5,7 +5,7 @@ Feature: Create New Library Scenario: Create a new library Given there is a running "" test network from scratch - When I make a "POST" request to "/api/invoke/createNewLibrary" on port 880 with: + When I make a "POST" request to "/api/invoke/createNewLibrary" on port 80 with: """ { "name": "Elizabeth's Library" @@ -26,7 +26,7 @@ Feature: Create New Library Scenario: Try to create a new library with a name that already exists Given there is a running "" test network Given there is a library with name "John's Library" - When I make a "POST" request to "/api/invoke/createNewLibrary" on port 880 with: + When I make a "POST" request to "/api/invoke/createNewLibrary" on port 80 with: """ { "name": "John's Library" diff --git a/chaincode/tests/features/getBooksByAuthor.feature b/chaincode/tests/features/getBooksByAuthor.feature index 519390a..a57aeef 100644 --- a/chaincode/tests/features/getBooksByAuthor.feature +++ b/chaincode/tests/features/getBooksByAuthor.feature @@ -7,7 +7,7 @@ Feature: Get Books By Author Scenario: Request an author with multiple books Given there is a running "" test network And there are 3 books with prefix "book" by author "Jack" - When I make a "GET" request to "/api/query/getBooksByAuthor" on port 880 with: + When I make a "GET" request to "/api/query/getBooksByAuthor" on port 80 with: """ { "authorName": "Jack" @@ -18,7 +18,7 @@ Feature: Get Books By Author Scenario: Request an author with no books Given there is a running "" test network - When I make a "GET" request to "/api/query/getBooksByAuthor" on port 880 with: + When I make a "GET" request to "/api/query/getBooksByAuthor" on port 80 with: """ { "authorName": "Mary" @@ -31,7 +31,7 @@ Feature: Get Books By Author Given there is a running "" test network Given there are 1 books with prefix "fantasy" by author "Missy" Given there are 2 books with prefix "cook" by author "John" - When I make a "GET" request to "/api/query/getBooksByAuthor" on port 880 with: + When I make a "GET" request to "/api/query/getBooksByAuthor" on port 80 with: """ { "authorName": "John" diff --git a/chaincode/tests/features/getNumberOfBooksFromLibrary.feature b/chaincode/tests/features/getNumberOfBooksFromLibrary.feature index 1177fd7..3c409fa 100644 --- a/chaincode/tests/features/getNumberOfBooksFromLibrary.feature +++ b/chaincode/tests/features/getNumberOfBooksFromLibrary.feature @@ -5,7 +5,7 @@ Feature: Get Number Of Books From Library Scenario: Query Get Number Of Books From Library that Exists Given there is a running "" test network - And I make a "POST" request to "/api/invoke/createAsset" on port 880 with: + And I make a "POST" request to "/api/invoke/createAsset" on port 80 with: """ { "asset": [ @@ -17,7 +17,7 @@ Feature: Get Number Of Books From Library ] } """ - And I make a "POST" request to "/api/invoke/createAsset" on port 880 with: + And I make a "POST" request to "/api/invoke/createAsset" on port 80 with: """ { "asset": [{ @@ -32,7 +32,7 @@ Feature: Get Number Of Books From Library }] } """ - When I make a "GET" request to "/api/query/getNumberOfBooksFromLibrary" on port 880 with: + When I make a "GET" request to "/api/query/getNumberOfBooksFromLibrary" on port 80 with: """ { "library": { @@ -51,7 +51,7 @@ Feature: Get Number Of Books From Library Scenario: Query Get Number Of Books From Library that Does Not Exists Given there is a running "" test network - When I make a "GET" request to "/api/query/getNumberOfBooksFromLibrary" on port 880 with: + When I make a "GET" request to "/api/query/getNumberOfBooksFromLibrary" on port 80 with: """ { "library": { diff --git a/chaincode/tests/features/updateBookTentant.feature b/chaincode/tests/features/updateBookTentant.feature index 2bcc90f..45b3264 100644 --- a/chaincode/tests/features/updateBookTentant.feature +++ b/chaincode/tests/features/updateBookTentant.feature @@ -6,7 +6,7 @@ Feature: Update Book Tentant Scenario: Update Book With A Existing Tentant # The first 3 statements will be used by all scenarios on this feature Given there is a running "" test network - And I make a "POST" request to "/api/invoke/createAsset" on port 880 with: + And I make a "POST" request to "/api/invoke/createAsset" on port 80 with: """ { "asset": [{ @@ -16,7 +16,7 @@ Feature: Update Book Tentant }] } """ - And I make a "POST" request to "/api/invoke/createAsset" on port 880 with: + And I make a "POST" request to "/api/invoke/createAsset" on port 80 with: """ { "asset": [{ @@ -26,7 +26,7 @@ Feature: Update Book Tentant }] } """ - When I make a "PUT" request to "/api/invoke/updateBookTenant" on port 880 with: + When I make a "PUT" request to "/api/invoke/updateBookTenant" on port 80 with: """ { "book": { @@ -55,7 +55,7 @@ Feature: Update Book Tentant Scenario: Update Book With A Not Existing Tentant Given there is a running "" test network - When I make a "PUT" request to "/api/invoke/updateBookTenant" on port 880 with: + When I make a "PUT" request to "/api/invoke/updateBookTenant" on port 80 with: """ { "book": { diff --git a/chaincode/tests/main_test.go b/chaincode/tests/main_test.go new file mode 100644 index 0000000..dea45f8 --- /dev/null +++ b/chaincode/tests/main_test.go @@ -0,0 +1,45 @@ +package tests + +import ( + "flag" + "os" + "testing" + + "github.com/cucumber/godog" + "github.com/cucumber/godog/colors" +) + +var opts = godog.Options{ + Output: colors.Colored(os.Stdout), + Format: Format(), + StopOnFailure: true, +} + +func init() { + godog.BindCommandLineFlags("godog.", &opts) +} + +func TestMain(m *testing.M) { + flag.Parse() + opts.Paths = flag.Args() + + status := godog.TestSuite{ + Name: "cc-tools-demo cucumber tests", + TestSuiteInitializer: InitializeTestSuite, + ScenarioInitializer: InitializeScenario, + Options: &opts, + }.Run() + + os.Exit(status) +} + +func Format() string { + format := "progress" + for _, arg := range os.Args[1:] { + if arg == "-test.v=true" { // go test transforms -v option + format = "pretty" + break + } + } + return format +} diff --git a/chaincode/tests/request_test.go b/chaincode/tests/request_test.go index fd19ebf..458a8ed 100644 --- a/chaincode/tests/request_test.go +++ b/chaincode/tests/request_test.go @@ -205,7 +205,7 @@ func thereAreBooksWithPrefixByAuthor(ctx context.Context, nBooks int, prefix str } dataAsBytes := bytes.NewBuffer([]byte(jsonStr)) - if res, err = http.Post("http://localhost:880/api/invoke/createAsset", "application/json", dataAsBytes); err != nil { + if res, err = http.Post("http://localhost:80/api/invoke/createAsset", "application/json", dataAsBytes); err != nil { return ctx, err } @@ -253,7 +253,7 @@ func thereIsALibraryWithName(ctx context.Context, name string) (context.Context, } // Create library if it doesnt exists - if len(received["result"].([]interface{})) == 0 { + if received["result"] == nil || len(received["result"].([]interface{})) == 0 { requestJSON = map[string]interface{}{ "asset": []interface{}{ map[string]interface{}{ @@ -268,7 +268,7 @@ func thereIsALibraryWithName(ctx context.Context, name string) (context.Context, } dataAsBytes = bytes.NewBuffer([]byte(jsonStr)) - if res, err = http.Post("http://localhost:880/api/invoke/createAsset", "application/json", dataAsBytes); err != nil { + if res, err = http.Post("http://localhost:80/api/invoke/createAsset", "application/json", dataAsBytes); err != nil { return ctx, err } @@ -282,7 +282,8 @@ func thereIsALibraryWithName(ctx context.Context, name string) (context.Context, func thereIsARunningTestNetworkFromScratch(arg1 string) error { // Start test network with 1 org only - cmd := exec.Command("../../startDev.sh", "-n", "1") + cmd := exec.Command("./startDev.sh", "-n", "1") + cmd.Dir = "../../" _, err := cmd.Output() @@ -292,7 +293,7 @@ func thereIsARunningTestNetworkFromScratch(arg1 string) error { } // Wait for ccapi - err = waitForNetwork("880") + err = waitForNetwork("80") if err != nil { fmt.Println(err.Error()) return err @@ -302,7 +303,7 @@ func thereIsARunningTestNetworkFromScratch(arg1 string) error { } func thereIsARunningTestNetwork(arg1 string) error { - if !verifyContainer("api.org.example.com", "3000") { + if !verifyContainer("ccapi.org.example.com", "80") { // Start test network with 1 org only cmd := exec.Command("../../startDev.sh", "-n", "1") @@ -314,7 +315,7 @@ func thereIsARunningTestNetwork(arg1 string) error { } // Wait for ccapi - err = waitForNetwork("880") + err = waitForNetwork("80") if err != nil { fmt.Println(err.Error()) return err @@ -323,6 +324,10 @@ func thereIsARunningTestNetwork(arg1 string) error { return nil } +func InitializeTestSuite(ctx *godog.TestSuiteContext) { + ctx.BeforeSuite(func() {}) +} + func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Step(`^I make a "([^"]*)" request to "([^"]*)" on port (\d+) with:$`, iMakeARequestToOnPortWith) ctx.Step(`^the response code should be (\d+)$`, theResponseCodeShouldBe) diff --git a/chaincode/txdefs/createNewLibrary.go b/chaincode/txdefs/createNewLibrary.go index 4c6e17b..77e3eb2 100644 --- a/chaincode/txdefs/createNewLibrary.go +++ b/chaincode/txdefs/createNewLibrary.go @@ -44,7 +44,7 @@ var CreateNewLibrary = tx.Transaction{ // Save the new library on channel _, err = libraryAsset.PutNew(stub) if err != nil { - return nil, errors.WrapError(err, "Error saving asset on blockchain") + return nil, errors.WrapErrorWithStatus(err, "Error saving asset on blockchain", err.Status()) } // Marshal asset back to JSON format diff --git a/chaincode/txdefs/getNumberOfBooksFromLibrary.go b/chaincode/txdefs/getNumberOfBooksFromLibrary.go index ed30596..8c56365 100644 --- a/chaincode/txdefs/getNumberOfBooksFromLibrary.go +++ b/chaincode/txdefs/getNumberOfBooksFromLibrary.go @@ -33,7 +33,7 @@ var GetNumberOfBooksFromLibrary = tx.Transaction{ // Returns Library from channel libraryMap, err := libraryKey.GetMap(stub) if err != nil { - return nil, errors.WrapError(err, "failed to get asset from the ledger") + return nil, errors.WrapErrorWithStatus(err, "failed to get asset from the ledger", err.Status()) } numberOfBooks := 0 diff --git a/chaincode/txdefs/updateBookTentant.go b/chaincode/txdefs/updateBookTentant.go index c86b81c..ca59927 100644 --- a/chaincode/txdefs/updateBookTentant.go +++ b/chaincode/txdefs/updateBookTentant.go @@ -46,14 +46,14 @@ var UpdateBookTenant = tx.Transaction{ // Returns Book from channel bookAsset, err := bookKey.Get(stub) if err != nil { - return nil, errors.WrapError(err, "failed to get asset from the ledger") + return nil, errors.WrapErrorWithStatus(err, "failed to get asset from the ledger", err.Status()) } bookMap := (map[string]interface{})(*bookAsset) // Returns person from channel tenantAsset, err := tenantKey.Get(stub) if err != nil { - return nil, errors.WrapError(err, "failed to get asset from the ledger") + return nil, errors.WrapErrorWithStatus(err, "failed to get asset from the ledger", err.Status()) } tenantMap := (map[string]interface{})(*tenantAsset) diff --git a/godog.sh b/godog.sh index 70a80bf..039ad9f 100755 --- a/godog.sh +++ b/godog.sh @@ -1,13 +1,8 @@ #!/usr/bin/env bash -which godog -if [ "$?" -ne 0 ]; then - echo "ERROR: godog tool not found. Please install it to run godog tests." - echo " Read more about the tool on: https://github.com/cucumber/godog" - echo "exiting..." - exit 1 -fi +echo "Starting development network ..." +# ./startDev.sh -n 1 echo "Running GoDog tests..." -echo "You may be prompted to insert system password for cert generation." -cd ./chaincode/tests; rm -rf ca channel-artifacts crypto-config; godog run; \ No newline at end of file +echo "Tests may take a few minutes to complete..." +cd ./chaincode/tests; go test;