Skip to content

Commit

Permalink
Merge pull request #2 from herzrasen/github-actions
Browse files Browse the repository at this point in the history
improved tests
  • Loading branch information
herzrasen authored Dec 23, 2022
2 parents 8ac5939 + 173bab7 commit 7bbab23
Show file tree
Hide file tree
Showing 15 changed files with 276 additions and 175 deletions.
25 changes: 25 additions & 0 deletions args/args.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package args

type RecordCmd struct {
Command string `arg:"positional"`
}

type ListCmd struct {
NoCount bool `arg:"--no-count"`
NoLastUpdate bool `arg:"--no-last-update"`
WithId bool `arg:"--with-id"`
Limit int `arg:"-l,--limit" default:"-1"`
}

type DeleteCmd struct {
Ids []int64 `arg:"-i,--id"`
Prefix string `arg:"-p,--prefix"`
MaxCount int64 `arg:"--max-count" help:"Delete all records with a count of at most max-count"`
}

type Args struct {
Record *RecordCmd `arg:"subcommand:record"`
List *ListCmd `arg:"subcommand:list"`
Delete *DeleteCmd `arg:"subcommand:delete"`
Config string `arg:"--config" default:"~/.config/hist/config.yml"`
}
6 changes: 3 additions & 3 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (

type Client struct {
Path string
db *sqlx.DB
Db *sqlx.DB
}

func NewClient(path string) (*Client, error) {
func NewSqliteClient(path string) (*Client, error) {
db, err := sqlx.Open("sqlite3", path)
if err != nil {
return nil, fmt.Errorf("client.NewClient: open: %w", err)
Expand All @@ -25,5 +25,5 @@ func NewClient(path string) (*Client, error) {
if err != nil {
return nil, fmt.Errorf("client.NewClient: create table: %w", err)
}
return &Client{Path: path, db: db}, nil
return &Client{Path: path, Db: db}, nil
}
11 changes: 6 additions & 5 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import (
)

func TestNewClient(t *testing.T) {
c, err := CreateTestClient(t)
require.NoError(t, err)
c := CreateTestClient(t)
defer os.Remove(c.Path)
rows, err := c.db.Query("SELECT name FROM sqlite_schema")
rows, err := c.Db.Query("SELECT name FROM sqlite_schema")
require.NoError(t, err)
defer rows.Close()
var tableExists bool
Expand All @@ -27,9 +26,11 @@ func TestNewClient(t *testing.T) {
}
}

func CreateTestClient(t *testing.T) (*Client, error) {
func CreateTestClient(t *testing.T) *Client {
t.Helper()
dbPath, err := os.CreateTemp("../", "test-*")
require.NoError(t, err)
return NewClient(dbPath.Name())
c, err := NewSqliteClient(dbPath.Name())
require.NoError(t, err)
return c
}
5 changes: 2 additions & 3 deletions client/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

const (
stmtDeleteByIds = `DELETE FROM hist WHERE id IN (?)`
stmtSelectByPrefix = `SELECT id, command, last_update, count FROM hist WHERE command LIKE ?`
stmtDeleteByPrefix = `DELETE FROM hist WHERE command LIKE ?`
)

Expand All @@ -30,7 +29,7 @@ func (c *Client) deleteByIds(options DeleteOptions) error {
if err != nil {
return fmt.Errorf("hist.Client.Delete: in: %w", err)
}
_, err = c.db.Exec(query, args...)
_, err = c.Db.Exec(query, args...)
if err != nil {
return fmt.Errorf("hist.Client.Delete: exec: %w", err)
}
Expand All @@ -39,7 +38,7 @@ func (c *Client) deleteByIds(options DeleteOptions) error {

func (c *Client) deleteByPrefix(options DeleteOptions) error {
prefix := fmt.Sprintf("%s%%", options.Prefix)
res, err := c.db.Exec(stmtDeleteByPrefix, prefix)
res, err := c.Db.Exec(stmtDeleteByPrefix, prefix)
if err != nil {
return fmt.Errorf("hist.Client.Delete: exec prefix: %w", err)
}
Expand Down
78 changes: 33 additions & 45 deletions client/delete_test.go
Original file line number Diff line number Diff line change
@@ -1,61 +1,49 @@
package client

import (
"github.com/stretchr/testify/assert"
"errors"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
"os"
"testing"
)

func TestClient_Delete(t *testing.T) {
t.Run("delete by prefix", func(t *testing.T) {
c, err := CreateTestClient(t)
db, mock, _ := sqlmock.New()
mock.ExpectExec("DELETE FROM hist WHERE command LIKE ?").
WithArgs("test-command%").
WillReturnResult(sqlmock.NewResult(0, 2))
c := Client{Db: sqlx.NewDb(db, "sqlite3")}
err := c.Delete(DeleteOptions{Prefix: "test-command"})
require.NoError(t, err)
defer os.Remove(c.Path)
if err = c.Update("test-command-1"); err != nil {
t.FailNow()
}
if err = c.Update("test-command-2"); err != nil {
t.FailNow()
}
if err = c.Update("test-command-1"); err != nil {
t.FailNow()
}
if err = c.Update("other-command"); err != nil {
t.FailNow()
}
err = c.Delete(DeleteOptions{Prefix: "test-command"})
require.NoError(t, err)
records, err := c.List(ListOptions{})
require.NoError(t, err)
assert.Len(t, records, 1)
})

t.Run("delete by ids", func(t *testing.T) {
c, err := CreateTestClient(t)
require.NoError(t, err)
defer os.Remove(c.Path)
if err = c.Update("test-command-1"); err != nil {
t.FailNow()
}
if err = c.Update("test-command-2"); err != nil {
t.FailNow()
}
if err = c.Update("other-command"); err != nil {
t.FailNow()
}
// get the entries to get some ids
records, err := c.List(ListOptions{})
require.NoError(t, err)
require.Len(t, records, 3)
err = c.Delete(DeleteOptions{Ids: []int64{
records[0].Id,
records[1].Id,
}})
require.NoError(t, err)
recordsAfterDelete, err := c.List(ListOptions{})
db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
mock.ExpectExec("DELETE FROM hist WHERE id IN (?, ?)").
WithArgs(100, 101).
WillReturnResult(sqlmock.NewResult(0, 2))
c := Client{Db: sqlx.NewDb(db, "sqlite3")}
err := c.Delete(DeleteOptions{Ids: []int64{100, 101}})
require.NoError(t, err)
assert.Len(t, recordsAfterDelete, 1)
assert.Equal(t, records[2], recordsAfterDelete[0])
})

t.Run("exec returns err with ids", func(t *testing.T) {
db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
mock.ExpectExec("DELETE FROM hist WHERE id IN (?, ?)").
WillReturnError(errors.New("some error"))
c := Client{Db: sqlx.NewDb(db, "sqlite3")}
err := c.Delete(DeleteOptions{Ids: []int64{100, 101}})
require.Error(t, err)
})

t.Run("exec returns err with prefix", func(t *testing.T) {
db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
mock.ExpectExec("DELETE FROM hist WHERE command LIKE ?").
WillReturnError(errors.New("some error"))
c := Client{Db: sqlx.NewDb(db, "sqlite3")}
err := c.Delete(DeleteOptions{Prefix: "test"})
require.Error(t, err)
})
}
20 changes: 7 additions & 13 deletions client/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package client

import (
"fmt"
"github.com/fatih/color"
"github.com/herzrasen/hist/record"
"github.com/jmoiron/sqlx"
log "github.com/sirupsen/logrus"
"sort"
"strings"
"time"
)

const (
Expand Down Expand Up @@ -39,7 +37,7 @@ func (c *Client) List(options ListOptions) ([]record.Record, error) {
return nil, fmt.Errorf("client.Client.List: build query: %w", err)
}

rows, err := c.db.Query(statement, args...)
rows, err := c.Db.Query(statement, args...)
if err != nil {
return nil, fmt.Errorf("client.Client.List: query: %w", err)
}
Expand Down Expand Up @@ -87,18 +85,14 @@ func (l *ListOptions) sort(records []record.Record) {
}

func (l *ListOptions) ToString(records []record.Record) string {
options := record.FormatOptions{
NoLastUpdate: l.NoLastUpdate,
NoCount: l.NoCount,
WithId: l.WithId,
}
buf := strings.Builder{}
for _, r := range records {
if !l.NoLastUpdate {
buf.WriteString(color.GreenString("%s\t", r.LastUpdate.Format(time.RFC1123)))
}
if !l.NoCount {
buf.WriteString(color.BlueString("%d\t", r.Count))
}
if l.WithId {
buf.WriteString(color.YellowString("%d\t", r.Id))
}
buf.WriteString(fmt.Sprintf("%s\n", r.Command))
buf.WriteString(fmt.Sprintf("%s\n", r.Format(options)))
}
return buf.String()
}
57 changes: 21 additions & 36 deletions client/list_test.go
Original file line number Diff line number Diff line change
@@ -1,59 +1,44 @@
package client

import (
"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
"testing"
"time"
)

func TestClient_List(t *testing.T) {
t.Run("succeed with empty ListOptions", func(t *testing.T) {
c, err := CreateTestClient(t)
require.NoError(t, err)
defer os.Remove(c.Path)
if err = c.Update("test-command-1"); err != nil {
t.FailNow()
}
time.Sleep(100 * time.Millisecond)
if err = c.Update("test-command-2"); err != nil {
t.FailNow()
}
time.Sleep(100 * time.Millisecond)
if err = c.Update("test-command-1"); err != nil {
t.FailNow()
}
db, mock, _ := sqlmock.New()
mock.ExpectQuery("SELECT id, command, last_update, count FROM hist ORDER BY last_update DESC").
WillReturnRows(sqlmock.NewRows([]string{"id", "command", "last_update", "count"}).
AddRow(1, "test-command-1", time.Now(), 42).
AddRow(2, "test-command-2", time.Now().Add(-1*time.Minute), 10))
c := Client{Db: sqlx.NewDb(db, "sqlite3")}
records, err := c.List(ListOptions{})
require.NoError(t, err)
assert.Len(t, records, 2)
r0 := records[0]
assert.Equal(t, "test-command-2", r0.Command)
assert.Equal(t, uint64(1), r0.Count)
assert.Equal(t, uint64(10), r0.Count)
r1 := records[1]
assert.Equal(t, "test-command-1", r1.Command)
assert.Equal(t, uint64(2), r1.Count)
assert.Equal(t, uint64(42), r1.Count)
})

t.Run("succeed with selection by ids", func(t *testing.T) {
c, err := CreateTestClient(t)
require.NoError(t, err)
defer os.Remove(c.Path)
if err = c.Update("test-command-1"); err != nil {
t.FailNow()
}
if err = c.Update("test-command-2"); err != nil {
t.FailNow()
}
if err = c.Update("test-command-3"); err != nil {
t.FailNow()
}
// select the records to get the ids
records, err := c.List(ListOptions{})
t.Run("succeed with specified ids", func(t *testing.T) {
db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
mock.ExpectQuery(`SELECT id, command, last_update, count
FROM hist WHERE id IN (?, ?, ?) ORDER BY last_update DESC`).
WillReturnRows(sqlmock.NewRows([]string{"id", "command", "last_update", "count"}))
c := Client{Db: sqlx.NewDb(db, "sqlite3")}
records, err := c.List(ListOptions{
Ids: []int64{100, 101, 102},
})
require.NoError(t, err)
assert.Len(t, records, 3)
ids := []int64{records[1].Id, records[2].Id}
recordsByIds, err := c.List(ListOptions{Ids: ids})
assert.Len(t, recordsByIds, 2)
assert.Len(t, records, 0)
})

}
2 changes: 1 addition & 1 deletion client/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const insertStmt = `INSERT INTO hist (command, last_update) VALUES (?, ?)
ON CONFLICT(command) DO UPDATE SET count=count+1, last_update=excluded.last_update`

func (c *Client) Update(command string) error {
_, err := c.db.Exec(insertStmt, command, time.Now())
_, err := c.Db.Exec(insertStmt, command, time.Now())
if err != nil {
return fmt.Errorf("hist.Client.Update: exec: %w", err)
}
Expand Down
45 changes: 45 additions & 0 deletions client/update_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package client

import (
"database/sql/driver"
"errors"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
"testing"
"time"
)

type AnyTime struct{}

// Match satisfies sqlmock.Argument interface
func (a AnyTime) Match(v driver.Value) bool {
_, ok := v.(time.Time)
return ok
}

func TestClient_Update(t *testing.T) {
t.Run("succeed", func(t *testing.T) {
db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
mock.ExpectExec(`INSERT INTO hist (command, last_update)
VALUES (?, ?)
ON CONFLICT(command)
DO UPDATE SET count=count+1, last_update=excluded.last_update`).
WithArgs("ls -alF", AnyTime{}).
WillReturnResult(sqlmock.NewResult(1, 1))
c := Client{Db: sqlx.NewDb(db, "sqlite3")}
err := c.Update("ls -alF")
require.NoError(t, err)
})
t.Run("succeed", func(t *testing.T) {
db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
mock.ExpectExec(`INSERT INTO hist (command, last_update)
VALUES (?, ?)
ON CONFLICT(command)
DO UPDATE SET count=count+1, last_update=excluded.last_update`).
WillReturnError(errors.New("some error"))
c := Client{Db: sqlx.NewDb(db, "sqlite3")}
err := c.Update("ls -alF")
require.Error(t, err)
})
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ require (
github.com/alexflint/go-arg v1.4.3
github.com/fatih/color v1.13.0
github.com/jmoiron/sqlx v1.3.5
github.com/lithammer/fuzzysearch v1.1.5
github.com/mattn/go-sqlite3 v1.14.16
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.7.0
)

require (
github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect
github.com/alexflint/go-scalar v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.1.0 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
Loading

0 comments on commit 7bbab23

Please sign in to comment.