Skip to content
This repository was archived by the owner on Jun 12, 2024. It is now read-only.

Commit 856919b

Browse files
authored
Improve documentation and tests (#13)
**What** - Revisited all the comments and improved them - Added unit tests for `BaseManager.GetPageInfo` - Removed unused files - Changed Readme
1 parent b440554 commit 856919b

File tree

13 files changed

+214
-52
lines changed

13 files changed

+214
-52
lines changed

README.md

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,5 @@
1-
# Contiamo Go Utils
1+
# Contiamo Go base
22

3-
## Fileutils
4-
5-
This contains utilities to make listing and copying files easier
6-
7-
## SQL
8-
9-
This package contains helpers for null time and uuid fields as well as for json maps and arrays
10-
11-
## Proto
12-
13-
This package contains an improved wrapper for the protobuf Timestamp that implements Scanner/Valuer so that it is easier to use with the SQL scan.
14-
15-
## Tracing
16-
17-
This package contains the various standard helpers and utilities for using [OpenTracing](https://opentracing.io/)
3+
This module contains common packages for Contiamo projects written in Go. Once, some of the projects introduce a pattern/approach which is worth re-using it's getting added here.
184

5+
This code is well-tested and has a very high test coverage.

cmd/.gitkeep

Whitespace-only changes.

pkg/crypto/encrypt.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ var (
1616
ErrCipherTooShort = errors.New("crypto: cipher plainText is too short for AES encryption")
1717
)
1818

19-
// PassphraseToKey converts a string to a key for encryption
19+
// PassphraseToKey converts a string to a key for encryption.
20+
//
21+
// This function must be used STRICTLY ONLY for generating
22+
// an encryption key out of a passphrase.
23+
// Please don't use this function for hashing user-provided values.
24+
// It uses SHA2 for simplicity but it's slower. User-provided data should use SHA3
25+
// because of its better performance.
2026
func PassphraseToKey(passphrase string) (key []byte) {
21-
// SHA512/256 will return exactly 32 bytes which is
27+
// SHA512/256 will return exactly 32 bytes which is exactly
2228
// the length of the key needed for AES256 encryption
2329
hash := sha512.Sum512_256([]byte(passphrase))
2430
return hash[:]

pkg/data/managers/base.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import (
1515
type PageInfo struct {
1616
// Total number of items
1717
ItemCount uint32 `json:"itemCount"`
18-
// Maximum items that can be on the page. They may be different from the requested number of times
18+
// Maximum items that can be on the page.
19+
// They may be different from the requested number of times
1920
ItemsPerPage uint32 `json:"itemsPerPage"`
2021
// Item count if filters were not applied
2122
UnfilteredItemCount uint32 `json:"unfilteredItemCount"`
@@ -31,6 +32,12 @@ type BaseManager interface {
3132
// GetTxQueryBuilder is the same as GetQueryBuilder but also opens a transaction
3233
GetTxQueryBuilder(ctx context.Context, opts *sql.TxOptions) (squirrel.StatementBuilderType, *sql.Tx, error)
3334
// GetPageInfo returns the page info object for a given page
35+
//
36+
// `scope` is the SQL where statement that defines the scope
37+
// of the query (e.g. organization_id)
38+
// `filter` is the SQL where statement that defines how the data is filtered by the user.
39+
// The user would not see any counts beyond the scope
40+
// but will see the total count beyond the filter
3441
GetPageInfo(ctx context.Context, table string, page parameters.Page, scope, filter squirrel.Sqlizer) (pageInfo PageInfo, err error)
3542
}
3643

@@ -61,8 +68,6 @@ func (m *baseManager) GetTxQueryBuilder(ctx context.Context, opts *sql.TxOptions
6168
}
6269

6370
func (m *baseManager) GetPageInfo(ctx context.Context, table string, page parameters.Page, scope, filter squirrel.Sqlizer) (pageInfo PageInfo, err error) {
64-
// TODO write unit tests for this function. It requires a data base server running
65-
6671
span, ctx := m.StartSpan(ctx, "GetPageInfo")
6772
defer func() {
6873
m.FinishSpan(span, err)

pkg/data/managers/base_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package managers
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"testing"
8+
"time"
9+
10+
"github.com/Masterminds/squirrel"
11+
dbtest "github.com/contiamo/go-base/pkg/db/test"
12+
"github.com/contiamo/go-base/pkg/http/parameters"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
const testTableName = "base_manager_test_table"
17+
18+
func TestGetPageInfo(t *testing.T) {
19+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
20+
defer cancel()
21+
_, db := dbtest.GetDatabase(t, createFixture)
22+
defer db.Close()
23+
24+
cases := []struct {
25+
name string
26+
table string
27+
page parameters.Page
28+
scope squirrel.Sqlizer
29+
filter squirrel.Sqlizer
30+
expected PageInfo
31+
expErr string
32+
}{
33+
{
34+
name: "Returns the valid page info when the scope and filter are set",
35+
table: testTableName,
36+
page: parameters.Page{Number: 1, Size: 5},
37+
scope: squirrel.Eq{"organization_id": 1},
38+
filter: squirrel.Eq{"cats_involved": true},
39+
expected: PageInfo{
40+
ItemsPerPage: 5,
41+
Current: 1,
42+
UnfilteredItemCount: 8,
43+
ItemCount: 4,
44+
},
45+
},
46+
{
47+
name: "Returns the valid page info when only the scope is set",
48+
table: testTableName,
49+
page: parameters.Page{Number: 1, Size: 5},
50+
scope: squirrel.Eq{"organization_id": 1},
51+
expected: PageInfo{
52+
ItemsPerPage: 5,
53+
Current: 1,
54+
UnfilteredItemCount: 8,
55+
ItemCount: 8,
56+
},
57+
},
58+
{
59+
name: "Returns the valid page info when only the filter is set",
60+
table: testTableName,
61+
page: parameters.Page{Number: 1, Size: 5},
62+
filter: squirrel.Eq{"cats_involved": true},
63+
expected: PageInfo{
64+
ItemsPerPage: 5,
65+
Current: 1,
66+
UnfilteredItemCount: 16,
67+
ItemCount: 8,
68+
},
69+
},
70+
{
71+
name: "Returns the valid page info when neither filter nor scope is set",
72+
table: testTableName,
73+
page: parameters.Page{Number: 1, Size: 5},
74+
expected: PageInfo{
75+
ItemsPerPage: 5,
76+
Current: 1,
77+
UnfilteredItemCount: 16,
78+
ItemCount: 16,
79+
},
80+
},
81+
{
82+
name: "Returns error when the table name is wrong",
83+
table: "wrong",
84+
page: parameters.Page{Number: 1, Size: 5},
85+
expErr: "pq: relation \"wrong\" does not exist",
86+
},
87+
}
88+
89+
for _, tc := range cases {
90+
t.Run(tc.name, func(t *testing.T) {
91+
m := NewBaseManager(db, "test")
92+
info, err := m.GetPageInfo(ctx, tc.table, tc.page, tc.scope, tc.filter)
93+
if tc.expErr != "" {
94+
require.Error(t, err)
95+
require.Equal(t, tc.expErr, err.Error())
96+
return
97+
}
98+
require.NoError(t, err)
99+
require.Equal(t, tc.expected, info)
100+
})
101+
}
102+
}
103+
104+
func createFixture(ctx context.Context, db *sql.DB) error {
105+
_, err := db.ExecContext(ctx, "CREATE TABLE "+testTableName+`(
106+
id integer PRIMARY KEY,
107+
organization_id integer NOT NULL,
108+
name text NOT NULL,
109+
description text NOT NULL,
110+
cats_involved boolean NOT NULL);`)
111+
112+
if err != nil {
113+
return err
114+
}
115+
116+
builder := squirrel.StatementBuilder.
117+
PlaceholderFormat(squirrel.Dollar).
118+
RunWith(db).
119+
Insert(testTableName).
120+
Columns("id", "organization_id", "name", "description", "cats_involved")
121+
122+
// 2 orgs with 8 values in each: 4 where cats are not involved and 4 where they are
123+
for i := 0; i < 2; i++ {
124+
for j := 0; j < 4; j++ {
125+
builder = builder.Values(
126+
10*i+j,
127+
i,
128+
fmt.Sprintf("name-%d", j),
129+
fmt.Sprintf("decr-%d", j),
130+
true,
131+
)
132+
}
133+
for j := 4; j < 8; j++ {
134+
builder = builder.Values(
135+
10*i+j,
136+
i,
137+
fmt.Sprintf("name-%d", j),
138+
fmt.Sprintf("decr-%d", j),
139+
false,
140+
)
141+
}
142+
}
143+
144+
_, err = builder.ExecContext(ctx)
145+
146+
return err
147+
}

pkg/data/managers/id_resolver.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ type IDResolver interface {
2323
Sqlizer(ctx context.Context, sql squirrel.StatementBuilderType, value string, filter squirrel.Sqlizer) (squirrel.Sqlizer, error)
2424
}
2525

26+
// NewIDResolver creates a new name->id resolver for a table, for example
27+
// var (
28+
// CollectionIDResolver = NewIDResolver("collections", "collection_id", "name")
29+
// TableIDResolver = NewIDResolver("tables", "table_id", "name")
30+
// )
31+
func NewIDResolver(table, idColumn, secondaryColumn string) IDResolver {
32+
return &idResolver{
33+
table: table,
34+
idColumn: idColumn,
35+
secondaryColumn: secondaryColumn,
36+
}
37+
}
38+
2639
type idResolver struct {
2740
table,
2841
idColumn,
@@ -94,16 +107,3 @@ func (r *idResolver) Resolve(ctx context.Context, sql squirrel.StatementBuilderT
94107

95108
return id, err
96109
}
97-
98-
// NewIDResolver creates a new name->id resolver for a table, for example
99-
// var (
100-
// CollectionIDResolver = NewIDResolver("collections", "collection_id", "name")
101-
// TableIDResolver = NewIDResolver("tables", "table_id", "name")
102-
// )
103-
func NewIDResolver(table, idColumn, secondaryColumn string) IDResolver {
104-
return &idResolver{
105-
table: table,
106-
idColumn: idColumn,
107-
secondaryColumn: secondaryColumn,
108-
}
109-
}

pkg/db/names.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ import (
88

99
// GenerateSQLName generates a unique safe name that can be used for a database or table name
1010
func GenerateSQLName() string {
11-
return "ds" + strings.Replace(uuid.NewV4().String(), "-", "", -1)
11+
// a SQL identifier can't start with a number
12+
return "s" + strings.Replace(uuid.NewV4().String(), "-", "", -1)
1213
}

pkg/db/open.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"github.com/contiamo/go-base/pkg/config"
99
)
1010

11-
// Open opens a postgres database and retries until ctx.Done()
11+
// Open opens a connection to a database and retries until ctx.Done()
1212
// The users must import all the necessary drivers before calling this function.
1313
func Open(ctx context.Context, cfg config.Database) (db *sql.DB, err error) {
1414
connStr, err := cfg.GetConnectionString()

pkg/db/serialization/json_blob.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"errors"
77
)
88

9-
// JSONBlob returns an Serializable using json serialization
9+
// JSONBlob returns an Serializable using json to []byte serialization
1010
func JSONBlob(value interface{}) Serializable {
1111
return jsonBlob{data: value}
1212
}

pkg/fileutils/list.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package fileutils
22

33
import "io/ioutil"
44

5-
// LS returns a list of the files in the given path, much like
6-
// the bash bommand `ls`
7-
func LS(path string) (fileNames []string) {
5+
// ListFiles returns a list of the files in the given path, much like
6+
// the bash command `ls`
7+
func ListFiles(path string) (fileNames []string) {
88
fileInfo, _ := ioutil.ReadDir(path)
99
for _, file := range fileInfo {
1010
fileNames = append(fileNames, file.Name())

0 commit comments

Comments
 (0)