Skip to content
This repository has been archived by the owner on Aug 11, 2020. It is now read-only.

Commit

Permalink
Merge pull request #30 from fhofherr/27-improve-tests
Browse files Browse the repository at this point in the history
Improve tests of pkg/db package
  • Loading branch information
fhofherr authored Sep 8, 2019
2 parents 30d2066 + 2e3f295 commit a7f3de3
Show file tree
Hide file tree
Showing 17 changed files with 737 additions and 182 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.bak
.coverage.out

.pebble/
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ GOLINT := golint
GOLANGCI_LINT := golangci-lint
DOCKER_COMPOSE := docker-compose
PROTOC := protoc
SED := sed

HOST_IP := $(shell $(GO) run scripts/dev/hostip/main.go)

Expand Down Expand Up @@ -39,7 +40,9 @@ $(COVERAGE_FILE): $(GO_FILES)
# would instrument dependencies as well.
#
# See https://golang.org/doc/go1.10#test
@$(GO) test -race -covermode=atomic -coverprofile=$(COVERAGE_FILE) -coverpkg=$(GO_PACKAGES) ./... 2> /dev/null
@$(GO) test -race -covermode=atomic -coverprofile=$@ -coverpkg=$(GO_PACKAGES) ./... 2> /dev/null
$(SED) -i.bak '/\.pb\.go/d' $@
$(SED) -i.bak '/testing\.go/d' $@

.PHONY: coverage
coverage: $(COVERAGE_FILE) ## Compute and display the current total code coverage
Expand Down Expand Up @@ -123,6 +126,7 @@ clean: ## Remove all intermediate directories and files
rm -rf $(BIN_DIR)
rm -rf $(PEBBLE_DIR)
rm -rf $(COVERAGE_FILE)
rm -rf $(COVERAGE_FILE).bak

.PHONY: help
help: ## Display this help message
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,27 @@ To shut down the test environment issue:
make dev-env-down
```

#### Firewall configuration

For the tests it is necessary that the instance of pebble running in
Docker is able to access the test server running on your host machine.
If you have a firewall it must allow access to port `5002`.

Furthermore the firewall must forward packages to the internal network
created by docker compose.

For `nftables` execute the following prior to executing the tests for
the first time.

```sh
sudo nft insert rule inet filter input tcp dport 5002 accept
sudo nft insert rule inet filter forward ip daddr 10.30.50.0/24 accept
```

The commands above assume you have a table called `filter` for the
`inet` address family. The `filter` table must define two chains
`input` and `forward`.

### Linter

`acmeproxy` uses GolangCI. The full report can be found
Expand Down
67 changes: 67 additions & 0 deletions pkg/db/bucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package db

import (
"encoding"

"github.com/pkg/errors"
"go.etcd.io/bbolt"
)

type bucket struct {
Bkt *bbolt.Bucket
Err error
}

func (b *bucket) writeRecord(id, record encoding.BinaryMarshaler) {
var (
idBytes []byte
recordBytes []byte
)
if b.Err != nil {
return
}
idBytes, b.Err = id.MarshalBinary()
if b.Err != nil {
return
}
recordBytes, b.Err = record.MarshalBinary()
if b.Err != nil {
return
}
b.Err = b.Bkt.Put(idBytes, recordBytes)
if b.Err != nil {
b.Err = errors.Wrap(b.Err, "put record")
return
}
}

func (b *bucket) readRecord(id encoding.BinaryMarshaler, target encoding.BinaryUnmarshaler) {
var (
idBytes []byte
recordBytes []byte
)
if b.Err != nil {
return
}
idBytes, b.Err = id.MarshalBinary()
if b.Err != nil {
return
}
recordBytes = b.Bkt.Get(idBytes)
if recordBytes == nil {
return
}
b.Err = target.UnmarshalBinary(recordBytes)
if b.Err != nil {
return
}
}

// do executes f iff b.Err is nil. do makes it easy to write code that should
// only execute if the bucket has no error yet.
func (b *bucket) do(f func() error) {
if b.Err != nil {
return
}
b.Err = f()
}
255 changes: 255 additions & 0 deletions pkg/db/bucket_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
package db

import (
"testing"

"github.com/fhofherr/acmeproxy/pkg/db/internal/dbrecords"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestWriteAndReadRecord(t *testing.T) {
var (
actual string
id = uuid.Must(uuid.NewRandom())
record = "some record"
bucketName = "some_bucket"
)

fx := NewTestFixture(t)
defer fx.Close()

err := fx.DB.updateBucket(bucketName, func(b *bucket) error {
b.writeRecord(id, &dbrecords.BinaryMarshaller{V: record})
return b.Err
})
if !assert.NoError(t, err) {
return
}
err = fx.DB.viewBucket(bucketName, func(b *bucket) error {
target := &dbrecords.BinaryUnmarshaller{V: &actual}
b.readRecord(id, target)
return b.Err
})

assert.NoError(t, err)
assert.Equal(t, record, actual)
}

func TestReadRecordCantMarshalID(t *testing.T) {
fx := NewTestFixture(t)
defer fx.Close()

marshalErr := errors.Errorf("can't marshal")
id := newMockBinaryMarshaller(t)
id.On("MarshalBinary").Return([]byte(nil), marshalErr)
target := newMockBinaryUnmarshaller(t)

bucketName := "some_bucket"
fx.CreateBucket(bucketName)
err := fx.DB.viewBucket(bucketName, func(b *bucket) error {
b.readRecord(id, target)
return b.Err
})

assert.Error(t, err)
assert.Equal(t, marshalErr, errors.Cause(err))
id.AssertCalled(t, "MarshalBinary")
target.AssertNotCalled(t, "UnmarshalBinary", mock.Anything)
}

func TestReadRecordCantUnmarshalRecord(t *testing.T) {
fx := NewTestFixture(t)
defer fx.Close()

record := "some record"
unmarshalErr := errors.New("can't unmarshal")
id := uuid.Must(uuid.NewRandom())
target := newMockBinaryUnmarshaller(t)
target.On("UnmarshalBinary", []byte(record)).Return(unmarshalErr)

bucketName := "some_bucket"
fx.CreateBucketWithKey(bucketName, id, &dbrecords.BinaryMarshaller{V: record})

err := fx.DB.viewBucket(bucketName, func(b *bucket) error {
b.readRecord(id, target)
return b.Err
})
assert.Error(t, err)
assert.Error(t, unmarshalErr, errors.Cause(err))
target.AssertCalled(t, "UnmarshalBinary", []byte(record))
}

func TestReadRecordDoesNothingIfBucketHasError(t *testing.T) {
fx := NewTestFixture(t)
defer fx.Close()

bucketErr := errors.Errorf("bucket error")
id := newMockBinaryMarshaller(t)
record := newMockBinaryUnmarshaller(t)

bucketName := "some_bucket"
fx.CreateBucket(bucketName)
err := fx.DB.viewBucket(bucketName, func(b *bucket) error {
b.Err = bucketErr
b.readRecord(id, record)
return b.Err
})

assert.Error(t, err)
assert.Equal(t, bucketErr, err)
}

func TestWriteRecordCantMarshalID(t *testing.T) {
fx := NewTestFixture(t)
defer fx.Close()

marshalErr := errors.Errorf("can't marshal id")
id := newMockBinaryMarshaller(t)
id.On("MarshalBinary").Return([]byte(nil), marshalErr)
record := newMockBinaryMarshaller(t)

err := fx.DB.updateBucket("some_bucket", func(b *bucket) error {
b.writeRecord(id, record)
return b.Err
})
assert.Error(t, err)
assert.Equal(t, marshalErr, errors.Cause(err))
id.AssertCalled(t, "MarshalBinary")
record.AssertNotCalled(t, "MarshalBinary")
}

func TestWriteRecordCantMarshalRecord(t *testing.T) {
fx := NewTestFixture(t)
defer fx.Close()

id := uuid.Must(uuid.NewRandom())
marshalErr := errors.Errorf("can't marshal record")
record := newMockBinaryMarshaller(t)
record.On("MarshalBinary").Return([]byte(nil), marshalErr)

err := fx.DB.updateBucket("some_bucket", func(b *bucket) error {
b.writeRecord(id, record)
return b.Err
})
assert.Error(t, err)
assert.Equal(t, marshalErr, errors.Cause(err))
record.AssertCalled(t, "MarshalBinary")
}

func TestWriteRecordPutToBucketFails(t *testing.T) {
fx := NewTestFixture(t)
defer fx.Close()

record := "some record"
err := fx.DB.updateBucket("some_bucket", func(b *bucket) error {
b.writeRecord(
// An empty id makes bolt fail, as the id is used as key which may
// not be empty.
&dbrecords.BinaryMarshaller{V: ""},
&dbrecords.BinaryMarshaller{V: record},
)
return b.Err
})
assert.Error(t, err)
}

func TestWriteRecordDoesNothingIfBucketHasError(t *testing.T) {
fx := NewTestFixture(t)
defer fx.Close()

bucketErr := errors.New("bucket error")
id := newMockBinaryMarshaller(t)
record := newMockBinaryMarshaller(t)
err := fx.DB.updateBucket("some_bucket", func(b *bucket) error {
b.Err = bucketErr
b.writeRecord(id, record)
return b.Err
})

assert.Error(t, err)
assert.Equal(t, bucketErr, err)
}

func TestDoExecutesCodeIffBucketIsOk(t *testing.T) {
tests := []struct {
name string
bucketError error
funcError error
funcCalled bool
}{
{
name: "bucket is ok",
funcCalled: true,
},
{
name: "bucket has error",
bucketError: errors.New("bucket error"),
funcCalled: false,
},
{
name: "function fails bucket",
funcError: errors.New("function error"),
funcCalled: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fx := NewTestFixture(t)
defer fx.Close()

bucketName := "some_bucket"
fx.CreateBucket(bucketName)

called := false
f := func() error {
called = true
return tt.funcError
}
err := fx.DB.viewBucket(bucketName, func(b *bucket) error {
b.Err = tt.bucketError
b.do(f)
return nil
})
if tt.funcError != nil {
assert.Equal(t, tt.funcError, err)
} else {
assert.Equal(t, tt.bucketError, err)
}
assert.Equal(t, tt.funcCalled, called)
})
}
}

type mockBinaryMarshaller struct {
mock.Mock
}

func newMockBinaryMarshaller(t *testing.T) *mockBinaryMarshaller {
m := &mockBinaryMarshaller{}
m.Test(t)
return m
}

func (m *mockBinaryMarshaller) MarshalBinary() ([]byte, error) {
args := m.Called()
return args.Get(0).([]byte), args.Error(1)
}

type mockBinaryUnmarshaller struct {
mock.Mock
}

func newMockBinaryUnmarshaller(t *testing.T) *mockBinaryUnmarshaller {
m := &mockBinaryUnmarshaller{}
m.Test(t)
return m
}

func (m *mockBinaryUnmarshaller) UnmarshalBinary(data []byte) error {
args := m.Called(data)
return args.Error(0)
}
Loading

0 comments on commit a7f3de3

Please sign in to comment.