Skip to content

Commit

Permalink
chore: additional usage examples (#18)
Browse files Browse the repository at this point in the history
Addresses issues:
- #16
- #15

Additionally:
- Got rid of some linting errors from pre-commit hooks
- Couple of instances of shadowing, file access etc, largely minor

Co-authored-by: 0xste <[email protected]>
  • Loading branch information
0xste and stefano-bd authored Nov 21, 2023
1 parent cd806b2 commit 4e290f5
Show file tree
Hide file tree
Showing 18 changed files with 218 additions and 37 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea
gocuke.iml
gocuke.iml
coverage.txt
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ linters-settings:
min-confidence: 0
gocyclo:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 10
min-complexity: 20
unused:
# treat code as a program (not a library) and report unused exported identifiers; default is false.
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
Expand Down
53 changes: 53 additions & 0 deletions _examples/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Example - demonstrates REST API server implementation tests.
package main

import (
"fmt"
"log"
"net/http"

"github.com/regen-network/gocuke"
)

// Server implements http.Server
type Server struct {
*http.Server
}

// NewServer creates a new instance of Server
func NewServer(port int, handler http.Handler) *Server {
addr := fmt.Sprintf(":%d", port)
srv := &http.Server{
Addr: addr,
Handler: handler,
}
return &Server{
Server: srv,
}
}

func getVersion(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
fail(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
data := struct {
Version string `json:"version"`
}{Version: gocuke.Version}
ok(w, data)
}

func main() {
// Define your handler
mux := http.NewServeMux()
mux.HandleFunc("/version", getVersion)

// Create a new server instance
server := NewServer(8080, mux)

// Start the server
fmt.Printf("Server running on port %s...\n", server.Addr)
if err := server.ListenAndServe(); err != nil {
log.Fatalf("Server failed to start: %v", err)
}
}
57 changes: 57 additions & 0 deletions _examples/api/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package main

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/regen-network/gocuke"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type suite struct {
// special arguments like TestingT are injected automatically into exported fields
gocuke.TestingT
resp *httptest.ResponseRecorder
}

func TestApi(t *testing.T) {
scope := &suite{TestingT: t, resp: httptest.NewRecorder()}
gocuke.NewRunner(t, scope).
Step(`^I send "(GET|POST|PUT|DELETE)" request to "([^"]*)"$`, scope.ISendRequestTo).
Step(`^the response code should be (\d+)$`, scope.TheResponseCodeShouldBe).
Step(`^the response should match json:$`, scope.TheResponseShouldMatchJson).
Run()
}

func (s *suite) ISendRequestTo(method string, endpoint string) {
req, err := http.NewRequest(method, endpoint, nil)
assert.Nil(s, err)

defer func() {
switch t := recover().(type) {
case string:
err = fmt.Errorf(t)
case error:
err = t
}
}()

switch endpoint {
case "/version":
getVersion(s.resp, req)
default:
err = fmt.Errorf("unknown endpoint: %s", endpoint)
}
assert.Nil(s, err)
}

func (s *suite) TheResponseCodeShouldBe(code int64) {
assert.Equalf(s, code, int64(s.resp.Code), "expected response code to be: %d, but actual is: %d", code, s.resp.Code)
}

func (s *suite) TheResponseShouldMatchJson(body gocuke.DocString) {
require.JSONEq(s, body.Content, s.resp.Body.String())
}
24 changes: 24 additions & 0 deletions _examples/api/features/version.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Feature: get version
In order to know gocuke version
As an API user
I need to be able to request version

Scenario: does not allow POST method
When I send "POST" request to "/version"
Then the response code should be 405
And the response should match json:
"""
{
"error": "Method not allowed"
}
"""

Scenario: should get version number
When I send "GET" request to "/version"
Then the response code should be 200
And the response should match json:
"""
{
"version": "v0.0.0-dev"
}
"""
31 changes: 31 additions & 0 deletions _examples/api/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"encoding/json"
"net/http"
)

// fail writes a json response with error msg and status header
func fail(w http.ResponseWriter, msg string, status int) {
w.WriteHeader(status)

data := struct {
Error string `json:"error"`
}{Error: msg}
resp, _ := json.Marshal(data)

w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}

// ok writes data to response with 200 status
func ok(w http.ResponseWriter, data interface{}) {
resp, err := json.Marshal(data)
if err != nil {
fail(w, "Oops something evil has happened", http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ Feature: data tables
Then it has 4 rows and 4 columns
And 3 rows as a header table
Then the values add up when accessed as a header table
And the total sum of the x + y + z column is 27
And the total sum of the x + y + z column is 27
18 changes: 10 additions & 8 deletions datatable_test.go → _examples/datatable/datatable_test.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
package gocuke
package datatable

import (
"gotest.tools/v3/assert"
"testing"

"github.com/regen-network/gocuke"
"gotest.tools/v3/assert"
)

func TestDataTable(t *testing.T) {
NewRunner(t, &dataTableSuite{}).
Path("features/datatable.feature").Run()
gocuke.NewRunner(t, &dataTableSuite{}).
Path("datatable.feature").Run()
}

type dataTableSuite struct {
TestingT
datatable DataTable
gocuke.TestingT
datatable gocuke.DataTable
total int64
}

func (d *dataTableSuite) Before(t TestingT) {
func (d *dataTableSuite) Before(t gocuke.TestingT) {
d.TestingT = t
}

func (s *dataTableSuite) ThisDataTable(a DataTable) {
func (s *dataTableSuite) ThisDataTable(a gocuke.DataTable) {
s.datatable = a
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ Feature: simple
Examples:
| x | y | z |
| 5 | 3 | 2 |
| 10 | 2 | 8 |
| 10 | 2 | 8 |
7 changes: 4 additions & 3 deletions custom_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package gocuke_test

import (
"github.com/regen-network/gocuke"
"github.com/stretchr/testify/require"
"regexp"
"testing"

"github.com/regen-network/gocuke"
"github.com/stretchr/testify/require"
)

func TestCustomSteps(t *testing.T) {
gocuke.NewRunner(t, &customStepsSuite{}).
Path("features/simple.feature").
Path("_examples/simple/simple.feature").
Step(`I have (\d+) cukes`, (*customStepsSuite).A).
Step(regexp.MustCompile(`I eat (\d+)`), (*customStepsSuite).B).
Step(`I have (\d+) left`, (*customStepsSuite).C).
Expand Down
3 changes: 1 addition & 2 deletions guess.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,9 @@ func lastChar(x string) byte {
}

func toFirstUpperIdentifier(str string) string {
runes := []rune(str)
var res []rune
isFirst := true
for _, r := range runes {
for _, r := range str {
if isFirst {
if !(unicode.IsLetter(r) || unicode.IsNumber(r)) {
continue
Expand Down
16 changes: 16 additions & 0 deletions mod_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gocuke

import (
"runtime/debug"
)

// Version of package - based on Semantic Versioning 2.0.0 http://semver.org/
var Version = "v0.0.0-dev"

func init() {
if info, available := debug.ReadBuildInfo(); available {
if Version == "v0.0.0-dev" && info.Main.Version != "(devel)" {
Version = info.Main.Version
}
}
}
11 changes: 4 additions & 7 deletions run.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,8 @@ func (r *Runner) Run() {
}

for _, file := range files {
f, err := os.Open(file)
f, err := os.Open(file) //nolint:gosec,EXC0010
assert.NilError(r.topLevelT, err)
defer func() {
err := f.Close()
if err != nil {
panic(err)
}
}()

haveTests = true

Expand All @@ -58,6 +52,9 @@ func (r *Runner) Run() {

(newDocRunner(r, doc)).runDoc(t)
})
if err = f.Close(); err != nil {
panic(err)
}
}
}

Expand Down
8 changes: 4 additions & 4 deletions run_step.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ func (r *scenarioRunner) runStep(step *messages.PickleStep, def *stepDef) {
// pickleArg goes last
if hasPickleArg {
i := expectedIn - 1
typ := typ.In(i)
pickleArgType := typ.In(i)
// only one of DataTable or DocString is valid
if typ == dataTableType {
if pickleArgType == dataTableType {
if step.Argument.DataTable == nil {
r.t.Fatalf("expected non-nil DataTable")
}
Expand All @@ -81,7 +81,7 @@ func (r *scenarioRunner) runStep(step *messages.PickleStep, def *stepDef) {
table: step.Argument.DataTable,
}
values[i] = reflect.ValueOf(dataTable)
} else if typ == docStringType {
} else if pickleArgType == docStringType {
if step.Argument.DocString == nil {
r.t.Fatalf("expected non-nil DocString")
}
Expand All @@ -92,7 +92,7 @@ func (r *scenarioRunner) runStep(step *messages.PickleStep, def *stepDef) {
}
values[i] = reflect.ValueOf(docString)
} else {
r.t.Fatalf("unexpected parameter type %v in function %s", typ, def.funcLoc)
r.t.Fatalf("unexpected parameter type %v in function %s", pickleArgType, def.funcLoc)
}
}

Expand Down
4 changes: 2 additions & 2 deletions simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func TestSimple(t *testing.T) {
gocuke.NewRunner(t, &simpleSuite{}).Path("features/simple.feature").Run()
gocuke.NewRunner(t, &simpleSuite{}).Path("_examples/simple/simple.feature").Run()
}

type simpleSuite struct {
Expand All @@ -31,7 +31,7 @@ func (s *simpleSuite) IHaveLeft(a int64) {

// test if a struct that doesn't use a pointer and a global var
func TestSimpleNonPointer(t *testing.T) {
gocuke.NewRunner(t, simpleSuiteNP{}).Path("features/simple.feature").Run()
gocuke.NewRunner(t, simpleSuiteNP{}).Path("_examples/simple/simple.feature").Run()
}

var globalCukes int64
Expand Down
9 changes: 5 additions & 4 deletions step_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package gocuke

import (
"fmt"
"gotest.tools/v3/assert"
"reflect"
"regexp"
"runtime"
"testing"

"gotest.tools/v3/assert"
)

type stepDef struct {
Expand Down Expand Up @@ -78,15 +79,15 @@ func (r *Runner) newStepDefOrHook(t *testing.T, exp *regexp.Regexp, f reflect.Va
}

for i := 0; i < typ.NumIn(); i++ {
typ := typ.In(i)
getter, ok := r.supportedSpecialArgs[typ]
stepTyp := typ.In(i)
getter, ok := r.supportedSpecialArgs[stepTyp]
if !ok {
// expect remaining args to be step arguments
break
}

def.specialArgs = append(def.specialArgs, &specialArg{
typ: typ,
typ: stepTyp,
getValue: getter,
})
}
Expand Down
2 changes: 1 addition & 1 deletion tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func initGlobalTagExpr() tag.Evaluatable {
globalTagExpr, err = tag.Parse(*flagTags)
if err != nil {
if err != nil {
panic(fmt.Errorf("error parsing tag expression %q: %v\n", *flagTags, err))
panic(fmt.Errorf("error parsing tag expression %q: %v", *flagTags, err))
}
}
}
Expand Down
Loading

0 comments on commit 4e290f5

Please sign in to comment.