Improve package structure (#207)

* Improve package structure

* Set up travis
This commit is contained in:
Sung Won Cho 2019-06-25 19:20:19 +10:00 committed by GitHub
commit 23a511dbe0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
171 changed files with 4750 additions and 4531 deletions

25
.travis.yml Normal file
View file

@ -0,0 +1,25 @@
language: go
dist: xenial
go:
- 1.12
addons:
postgresql: "10"
env:
- NODE_VERSION=10.15.0
before_script:
- nvm install "$NODE_VERSION"
- nvm use "$NODE_VERSION"
- node --version
- psql -c "CREATE DATABASE dnote_test;" -U postgres
install:
- make install
script:
- make test-cli
- make test-api
- make test-web

56
Makefile Normal file
View file

@ -0,0 +1,56 @@
DEP := $(shell command -v dep 2> /dev/null)
NPM := $(shell command -v npm 2> /dev/null)
## installation
install-cli:
ifndef DEP
@echo "==> installing dep"
@curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
endif
@echo "==> installing CLI dependencies"
@(cd ${GOPATH}/src/github.com/dnote/dnote/pkg/cli && dep ensure)
.PHONY: install-cli
install-web:
ifndef NPM
@echo "npm not found"
exit 1
endif
@echo "==> installing web dependencies"
@(cd ${GOPATH}/src/github.com/dnote/dnote/web && npm install)
.PHONY: install-web
install-server:
@echo "==> installing server dependencies"
@(cd ${GOPATH}/src/github.com/dnote/dnote/pkg/server && dep ensure)
.PHONY: install-server
install: install-cli install-web install-server
.PHONY: install
## test
test-cli:
@echo "==> running CLI test"
@${GOPATH}/src/github.com/dnote/dnote/pkg/cli/scripts/test.sh
.PHONY: test-cli
test-api:
@echo "==> running API test"
@${GOPATH}/src/github.com/dnote/dnote/pkg/server/api/scripts/test-local.sh
.PHONY: test-api
test-web:
@echo "==> running web test"
@(cd ${GOPATH}/src/github.com/dnote/dnote/web && npm run test)
.PHONY: test-web
test: test-cli test-api test-web
.PHONY: test
## build
build-web:
@echo "==> building web"
@${GOPATH}/src/github.com/dnote/dnote/web/scripts/build-prod.sh
.PHONY: build-web

View file

@ -3,7 +3,7 @@
Dnote is a simple notebook for developers.
[![Build Status](https://semaphoreci.com/api/v1/dnote/dnote-2/branches/master/badge.svg)](https://semaphoreci.com/dnote/dnote-2)
[![Build Status](https://travis-ci.org/dnote/dnote.svg?branch=master)](https://travis-ci.org/dnote/dnote)
## What is Dnote?

View file

@ -1,358 +0,0 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package core
import (
"database/sql"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/cli/utils"
"github.com/pkg/errors"
"github.com/satori/go.uuid"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)
var (
// ConfigFilename is the name of the config file
ConfigFilename = "dnoterc"
// TmpContentFilename is the name of the temporary file that holds editor input
TmpContentFilename = "DNOTE_TMPCONTENT.md"
)
// RunEFunc is a function type of dnote commands
type RunEFunc func(*cobra.Command, []string) error
// GetConfigPath returns the path to the dnote config file
func GetConfigPath(ctx infra.DnoteCtx) string {
return fmt.Sprintf("%s/%s", ctx.DnoteDir, ConfigFilename)
}
// GetDnoteTmpContentPath returns the path to the temporary file containing
// content being added or edited
func GetDnoteTmpContentPath(ctx infra.DnoteCtx) string {
return fmt.Sprintf("%s/%s", ctx.DnoteDir, TmpContentFilename)
}
// GetBookUUID returns a uuid of a book given a label
func GetBookUUID(ctx infra.DnoteCtx, label string) (string, error) {
db := ctx.DB
var ret string
err := db.QueryRow("SELECT uuid FROM books WHERE label = ?", label).Scan(&ret)
if err == sql.ErrNoRows {
return ret, errors.Errorf("book '%s' not found", label)
} else if err != nil {
return ret, errors.Wrap(err, "querying the book")
}
return ret, nil
}
// getEditorCommand returns the system's editor command with appropriate flags,
// if necessary, to make the command wait until editor is close to exit.
func getEditorCommand() string {
editor := os.Getenv("EDITOR")
var ret string
switch editor {
case "atom":
ret = "atom -w"
case "subl":
ret = "subl -n -w"
case "mate":
ret = "mate -w"
case "vim":
ret = "vim"
case "nano":
ret = "nano"
case "emacs":
ret = "emacs"
case "nvim":
ret = "nvim"
default:
ret = "vi"
}
return ret
}
// InitFiles creates, if necessary, the dnote directory and files inside
func InitFiles(ctx infra.DnoteCtx) error {
if err := initDnoteDir(ctx); err != nil {
return errors.Wrap(err, "creating the dnote dir")
}
if err := initConfigFile(ctx); err != nil {
return errors.Wrap(err, "generating the config file")
}
return nil
}
// initConfigFile populates a new config file if it does not exist yet
func initConfigFile(ctx infra.DnoteCtx) error {
path := GetConfigPath(ctx)
if utils.FileExists(path) {
return nil
}
editor := getEditorCommand()
config := infra.Config{
Editor: editor,
}
b, err := yaml.Marshal(config)
if err != nil {
return errors.Wrap(err, "marshalling config into YAML")
}
err = ioutil.WriteFile(path, b, 0644)
if err != nil {
return errors.Wrap(err, "writing the config file")
}
return nil
}
// initDnoteDir initializes dnote directory if it does not exist yet
func initDnoteDir(ctx infra.DnoteCtx) error {
path := ctx.DnoteDir
if utils.FileExists(path) {
return nil
}
if err := os.MkdirAll(path, 0755); err != nil {
return errors.Wrap(err, "Failed to create dnote directory")
}
return nil
}
// WriteConfig writes the config to the config file
func WriteConfig(ctx infra.DnoteCtx, config infra.Config) error {
d, err := yaml.Marshal(config)
if err != nil {
return errors.Wrap(err, "marhsalling config")
}
configPath := GetConfigPath(ctx)
err = ioutil.WriteFile(configPath, d, 0644)
if err != nil {
errors.Wrap(err, "writing the config file")
}
return nil
}
// LogAction logs action and updates the last_action
func LogAction(tx *sql.Tx, schema int, actionType, data string, timestamp int64) error {
uuid := uuid.NewV4().String()
_, err := tx.Exec(`INSERT INTO actions (uuid, schema, type, data, timestamp)
VALUES (?, ?, ?, ?, ?)`, uuid, schema, actionType, data, timestamp)
if err != nil {
return errors.Wrap(err, "inserting an action")
}
_, err = tx.Exec("UPDATE system SET value = ? WHERE key = ?", timestamp, "last_action")
if err != nil {
return errors.Wrap(err, "updating last_action")
}
return nil
}
// ReadConfig reads the config file
func ReadConfig(ctx infra.DnoteCtx) (infra.Config, error) {
var ret infra.Config
configPath := GetConfigPath(ctx)
b, err := ioutil.ReadFile(configPath)
if err != nil {
return ret, errors.Wrap(err, "reading config file")
}
err = yaml.Unmarshal(b, &ret)
if err != nil {
return ret, errors.Wrap(err, "unmarshalling config")
}
return ret, nil
}
// SanitizeContent sanitizes note content
func SanitizeContent(s string) string {
var ret string
ret = strings.Trim(s, " ")
// Remove newline at the end of the file because POSIX defines a line as
// characters followed by a newline
ret = strings.TrimSuffix(ret, "\n")
ret = strings.TrimSuffix(ret, "\r\n")
return ret
}
func newEditorCmd(ctx infra.DnoteCtx, fpath string) (*exec.Cmd, error) {
config, err := ReadConfig(ctx)
if err != nil {
return nil, errors.Wrap(err, "reading config")
}
args := strings.Fields(config.Editor)
args = append(args, fpath)
return exec.Command(args[0], args[1:]...), nil
}
// GetEditorInput gets the user input by launching a text editor and waiting for
// it to exit
func GetEditorInput(ctx infra.DnoteCtx, fpath string, content *string) error {
if !utils.FileExists(fpath) {
f, err := os.Create(fpath)
if err != nil {
return errors.Wrap(err, "creating a temporary content file")
}
err = f.Close()
if err != nil {
return errors.Wrap(err, "closing the temporary content file")
}
}
cmd, err := newEditorCmd(ctx, fpath)
if err != nil {
return errors.Wrap(err, "creating an editor command")
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Start()
if err != nil {
return errors.Wrapf(err, "launching an editor")
}
err = cmd.Wait()
if err != nil {
return errors.Wrap(err, "waiting for the editor")
}
b, err := ioutil.ReadFile(fpath)
if err != nil {
return errors.Wrap(err, "reading the temporary content file")
}
err = os.Remove(fpath)
if err != nil {
return errors.Wrap(err, "removing the temporary content file")
}
raw := string(b)
c := SanitizeContent(raw)
*content = c
return nil
}
func initSystemKV(db *infra.DB, key string, val string) error {
var count int
if err := db.QueryRow("SELECT count(*) FROM system WHERE key = ?", key).Scan(&count); err != nil {
return errors.Wrapf(err, "counting %s", key)
}
if count > 0 {
return nil
}
if _, err := db.Exec("INSERT INTO system (key, value) VALUES (?, ?)", key, val); err != nil {
db.Rollback()
return errors.Wrapf(err, "inserting %s %s", key, val)
}
return nil
}
// InitSystem inserts system data if missing
func InitSystem(ctx infra.DnoteCtx) error {
db := ctx.DB
tx, err := db.Begin()
if err != nil {
return errors.Wrap(err, "beginning a transaction")
}
nowStr := strconv.FormatInt(time.Now().Unix(), 10)
if err := initSystemKV(tx, infra.SystemLastUpgrade, nowStr); err != nil {
return errors.Wrapf(err, "initializing system config for %s", infra.SystemLastUpgrade)
}
if err := initSystemKV(tx, infra.SystemLastMaxUSN, "0"); err != nil {
return errors.Wrapf(err, "initializing system config for %s", infra.SystemLastMaxUSN)
}
if err := initSystemKV(tx, infra.SystemLastSyncAt, "0"); err != nil {
return errors.Wrapf(err, "initializing system config for %s", infra.SystemLastSyncAt)
}
tx.Commit()
return nil
}
// GetValidSession returns a session key from the local storage if one exists and is not expired
// If one does not exist or is expired, it prints out an instruction and returns false
func GetValidSession(ctx infra.DnoteCtx) (string, bool, error) {
db := ctx.DB
var sessionKey string
var sessionKeyExpires int64
if err := GetSystem(db, infra.SystemSessionKey, &sessionKey); err != nil {
return "", false, errors.Wrap(err, "getting session key")
}
if err := GetSystem(db, infra.SystemSessionKeyExpiry, &sessionKeyExpires); err != nil {
return "", false, errors.Wrap(err, "getting session key expiry")
}
if sessionKey == "" {
log.Error("login required. please run `dnote login`\n")
return "", false, nil
}
if sessionKeyExpires < time.Now().Unix() {
log.Error("sesison expired. please run `dnote login`\n")
return "", false, nil
}
return sessionKey, true, nil
}

View file

@ -1,56 +0,0 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package core
import (
"database/sql"
"github.com/dnote/dnote/cli/infra"
"github.com/pkg/errors"
)
// NoteInfo is a basic information about a note
type NoteInfo struct {
RowID int
BookLabel string
UUID string
Content string
AddedOn int64
EditedOn int64
}
// GetNoteInfo returns a NoteInfo for the note with the given noteRowID
func GetNoteInfo(ctx infra.DnoteCtx, noteRowID string) (NoteInfo, error) {
var ret NoteInfo
db := ctx.DB
err := db.QueryRow(`SELECT books.label, notes.uuid, notes.body, notes.added_on, notes.edited_on, notes.rowid
FROM notes
INNER JOIN books ON books.uuid = notes.book_uuid
WHERE notes.rowid = ? AND notes.deleted = false`, noteRowID).
Scan(&ret.BookLabel, &ret.UUID, &ret.Content, &ret.AddedOn, &ret.EditedOn, &ret.RowID)
if err == sql.ErrNoRows {
return ret, errors.Errorf("note %s not found", noteRowID)
} else if err != nil {
return ret, errors.Wrap(err, "querying the note")
}
return ret, nil
}

View file

@ -1,225 +0,0 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
// Package infra defines dnote structure
package infra
import (
"database/sql"
"encoding/base64"
"fmt"
"os"
"os/user"
// use sqlite
_ "github.com/mattn/go-sqlite3"
"github.com/pkg/errors"
)
var (
// DnoteDirName is the name of the directory containing dnote files
DnoteDirName = ".dnote"
// SystemSchema is the key for schema in the system table
SystemSchema = "schema"
// SystemRemoteSchema is the key for remote schema in the system table
SystemRemoteSchema = "remote_schema"
// SystemLastSyncAt is the timestamp of the server at the last sync
SystemLastSyncAt = "last_sync_time"
// SystemLastMaxUSN is the user's max_usn from the server at the alst sync
SystemLastMaxUSN = "last_max_usn"
// SystemLastUpgrade is the timestamp at which the system more recently checked for an upgrade
SystemLastUpgrade = "last_upgrade"
// SystemCipherKey is the encryption key
SystemCipherKey = "enc_key"
// SystemSessionKey is the session key
SystemSessionKey = "session_token"
// SystemSessionKeyExpiry is the timestamp at which the session key will expire
SystemSessionKeyExpiry = "session_token_expiry"
)
// DnoteCtx is a context holding the information of the current runtime
type DnoteCtx struct {
HomeDir string
DnoteDir string
APIEndpoint string
Version string
DB *DB
SessionKey string
SessionKeyExpiry int64
CipherKey []byte
}
// Config holds dnote configuration
type Config struct {
Editor string
}
// NewCtx returns a new dnote context
func NewCtx(apiEndpoint, versionTag string) (DnoteCtx, error) {
homeDir, err := getHomeDir()
if err != nil {
return DnoteCtx{}, errors.Wrap(err, "Failed to get home dir")
}
dnoteDir := getDnoteDir(homeDir)
dnoteDBPath := fmt.Sprintf("%s/dnote.db", dnoteDir)
db, err := OpenDB(dnoteDBPath)
if err != nil {
return DnoteCtx{}, errors.Wrap(err, "conntecting to db")
}
ret := DnoteCtx{
HomeDir: homeDir,
DnoteDir: dnoteDir,
APIEndpoint: apiEndpoint,
Version: versionTag,
DB: db,
}
return ret, nil
}
// SetupCtx populates context and returns a new context
func SetupCtx(ctx DnoteCtx) (DnoteCtx, error) {
db := ctx.DB
var sessionKey, cipherKeyB64 string
var sessionKeyExpiry int64
err := db.QueryRow("SELECT value FROM system WHERE key = ?", SystemSessionKey).Scan(&sessionKey)
if err != nil && err != sql.ErrNoRows {
return ctx, errors.Wrap(err, "finding sesison key")
}
err = db.QueryRow("SELECT value FROM system WHERE key = ?", SystemCipherKey).Scan(&cipherKeyB64)
if err != nil && err != sql.ErrNoRows {
return ctx, errors.Wrap(err, "finding sesison key")
}
err = db.QueryRow("SELECT value FROM system WHERE key = ?", SystemSessionKeyExpiry).Scan(&sessionKeyExpiry)
if err != nil && err != sql.ErrNoRows {
return ctx, errors.Wrap(err, "finding sesison key expiry")
}
cipherKey, err := base64.StdEncoding.DecodeString(cipherKeyB64)
if err != nil {
return ctx, errors.Wrap(err, "decoding cipherKey from base64")
}
ret := DnoteCtx{
HomeDir: ctx.HomeDir,
DnoteDir: ctx.DnoteDir,
APIEndpoint: ctx.APIEndpoint,
Version: ctx.Version,
DB: ctx.DB,
SessionKey: sessionKey,
SessionKeyExpiry: sessionKeyExpiry,
CipherKey: cipherKey,
}
return ret, nil
}
func getDnoteDir(homeDir string) string {
var ret string
dnoteDirEnv := os.Getenv("DNOTE_DIR")
if dnoteDirEnv == "" {
ret = fmt.Sprintf("%s/%s", homeDir, DnoteDirName)
} else {
ret = dnoteDirEnv
}
return ret
}
func getHomeDir() (string, error) {
homeDirEnv := os.Getenv("DNOTE_HOME_DIR")
if homeDirEnv != "" {
return homeDirEnv, nil
}
usr, err := user.Current()
if err != nil {
return "", errors.Wrap(err, "Failed to get current user")
}
return usr.HomeDir, nil
}
// InitDB initializes the database.
// Ideally this process must be a part of migration sequence. But it is performed
// seaprately because it is a prerequisite for legacy migration.
func InitDB(ctx DnoteCtx) error {
db := ctx.DB
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS notes
(
id integer PRIMARY KEY AUTOINCREMENT,
uuid text NOT NULL,
book_uuid text NOT NULL,
content text NOT NULL,
added_on integer NOT NULL,
edited_on integer DEFAULT 0,
public bool DEFAULT false
)`)
if err != nil {
return errors.Wrap(err, "creating notes table")
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS books
(
uuid text PRIMARY KEY,
label text NOT NULL
)`)
if err != nil {
return errors.Wrap(err, "creating books table")
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS system
(
key string NOT NULL,
value text NOT NULL
)`)
if err != nil {
return errors.Wrap(err, "creating system table")
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS actions
(
uuid text PRIMARY KEY,
schema integer NOT NULL,
type text NOT NULL,
data text NOT NULL,
timestamp integer NOT NULL
)`)
if err != nil {
return errors.Wrap(err, "creating actions table")
}
_, err = db.Exec(`
CREATE UNIQUE INDEX IF NOT EXISTS idx_books_label ON books(label);
CREATE UNIQUE INDEX IF NOT EXISTS idx_notes_uuid ON notes(uuid);
CREATE UNIQUE INDEX IF NOT EXISTS idx_books_uuid ON books(uuid);
CREATE INDEX IF NOT EXISTS idx_notes_book_uuid ON notes(book_uuid);`)
if err != nil {
return errors.Wrap(err, "creating indices")
}
return nil
}

View file

@ -1,80 +0,0 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"os"
"github.com/dnote/dnote/cli/cmd/root"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
_ "github.com/mattn/go-sqlite3"
"github.com/pkg/errors"
// commands
"github.com/dnote/dnote/cli/cmd/add"
"github.com/dnote/dnote/cli/cmd/cat"
"github.com/dnote/dnote/cli/cmd/edit"
"github.com/dnote/dnote/cli/cmd/find"
"github.com/dnote/dnote/cli/cmd/login"
"github.com/dnote/dnote/cli/cmd/logout"
"github.com/dnote/dnote/cli/cmd/ls"
"github.com/dnote/dnote/cli/cmd/remove"
"github.com/dnote/dnote/cli/cmd/sync"
"github.com/dnote/dnote/cli/cmd/version"
"github.com/dnote/dnote/cli/cmd/view"
)
// apiEndpoint and versionTag are populated during link time
var apiEndpoint string
var versionTag = "master"
func main() {
ctx, err := infra.NewCtx(apiEndpoint, versionTag)
if err != nil {
panic(errors.Wrap(err, "initializing context"))
}
defer ctx.DB.Close()
if err := root.Prepare(ctx); err != nil {
panic(errors.Wrap(err, "preparing dnote run"))
}
ctx, err = infra.SetupCtx(ctx)
if err != nil {
panic(errors.Wrap(err, "setting up context"))
}
root.Register(remove.NewCmd(ctx))
root.Register(edit.NewCmd(ctx))
root.Register(login.NewCmd(ctx))
root.Register(logout.NewCmd(ctx))
root.Register(add.NewCmd(ctx))
root.Register(ls.NewCmd(ctx))
root.Register(sync.NewCmd(ctx))
root.Register(version.NewCmd(ctx))
root.Register(cat.NewCmd(ctx))
root.Register(view.NewCmd(ctx))
root.Register(find.NewCmd(ctx))
if err := root.Execute(); err != nil {
log.Errorf("%s\n", err.Error())
os.Exit(1)
}
}

View file

@ -1,339 +0,0 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"log"
"os"
"os/exec"
"testing"
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/testutils"
"github.com/dnote/dnote/cli/utils"
"github.com/pkg/errors"
)
var binaryName = "test-dnote"
func TestMain(m *testing.M) {
if err := exec.Command("go", "build", "--tags", "fts5", "-o", binaryName).Run(); err != nil {
log.Print(errors.Wrap(err, "building a binary").Error())
os.Exit(1)
}
os.Exit(m.Run())
}
func TestInit(t *testing.T) {
// Set up
ctx := testutils.InitEnv(t, "./tmp", "./testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
// Execute
testutils.RunDnoteCmd(t, ctx, binaryName)
// Test
if !utils.FileExists(ctx.DnoteDir) {
t.Errorf("dnote directory was not initialized")
}
if !utils.FileExists(fmt.Sprintf("%s/%s", ctx.DnoteDir, core.ConfigFilename)) {
t.Errorf("config file was not initialized")
}
db := ctx.DB
var notesTableCount, booksTableCount, systemTableCount int
testutils.MustScan(t, "counting notes",
db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type = ? AND name = ?", "table", "notes"), &notesTableCount)
testutils.MustScan(t, "counting books",
db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type = ? AND name = ?", "table", "books"), &booksTableCount)
testutils.MustScan(t, "counting system",
db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type = ? AND name = ?", "table", "system"), &systemTableCount)
testutils.AssertEqual(t, notesTableCount, 1, "notes table count mismatch")
testutils.AssertEqual(t, booksTableCount, 1, "books table count mismatch")
testutils.AssertEqual(t, systemTableCount, 1, "system table count mismatch")
// test that all default system configurations are generated
var lastUpgrade, lastMaxUSN, lastSyncAt string
testutils.MustScan(t, "scanning last upgrade",
db.QueryRow("SELECT value FROM system WHERE key = ?", infra.SystemLastUpgrade), &lastUpgrade)
testutils.MustScan(t, "scanning last max usn",
db.QueryRow("SELECT value FROM system WHERE key = ?", infra.SystemLastMaxUSN), &lastMaxUSN)
testutils.MustScan(t, "scanning last sync at",
db.QueryRow("SELECT value FROM system WHERE key = ?", infra.SystemLastSyncAt), &lastSyncAt)
testutils.AssertNotEqual(t, lastUpgrade, "", "last upgrade should not be empty")
testutils.AssertNotEqual(t, lastMaxUSN, "", "last max usn should not be empty")
testutils.AssertNotEqual(t, lastSyncAt, "", "last sync at should not be empty")
}
func TestAddNote_NewBook_BodyFlag(t *testing.T) {
// Set up
ctx := testutils.InitEnv(t, "./tmp", "./testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
// Execute
testutils.RunDnoteCmd(t, ctx, binaryName, "add", "js", "-c", "foo")
// Test
db := ctx.DB
var noteCount, bookCount int
testutils.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
testutils.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
testutils.AssertEqualf(t, bookCount, 1, "book count mismatch")
testutils.AssertEqualf(t, noteCount, 1, "note count mismatch")
var book core.Book
testutils.MustScan(t, "getting book", db.QueryRow("SELECT uuid, dirty FROM books where label = ?", "js"), &book.UUID, &book.Dirty)
var note core.Note
testutils.MustScan(t, "getting note",
db.QueryRow("SELECT uuid, body, added_on, dirty FROM notes where book_uuid = ?", book.UUID), &note.UUID, &note.Body, &note.AddedOn, &note.Dirty)
testutils.AssertEqual(t, book.Dirty, true, "Book dirty mismatch")
testutils.AssertNotEqual(t, note.UUID, "", "Note should have UUID")
testutils.AssertEqual(t, note.Body, "foo", "Note body mismatch")
testutils.AssertEqual(t, note.Dirty, true, "Note dirty mismatch")
testutils.AssertNotEqual(t, note.AddedOn, int64(0), "Note added_on mismatch")
}
func TestAddNote_ExistingBook_BodyFlag(t *testing.T) {
// Set up
ctx := testutils.InitEnv(t, "./tmp", "./testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
testutils.Setup3(t, ctx)
// Execute
testutils.RunDnoteCmd(t, ctx, binaryName, "add", "js", "-c", "foo")
// Test
db := ctx.DB
var noteCount, bookCount int
testutils.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
testutils.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
testutils.AssertEqualf(t, bookCount, 1, "book count mismatch")
testutils.AssertEqualf(t, noteCount, 2, "note count mismatch")
var n1, n2 core.Note
testutils.MustScan(t, "getting n1",
db.QueryRow("SELECT uuid, body, added_on, dirty FROM notes WHERE book_uuid = ? AND uuid = ?", "js-book-uuid", "43827b9a-c2b0-4c06-a290-97991c896653"), &n1.UUID, &n1.Body, &n1.AddedOn, &n1.Dirty)
testutils.MustScan(t, "getting n2",
db.QueryRow("SELECT uuid, body, added_on, dirty FROM notes WHERE book_uuid = ? AND body = ?", "js-book-uuid", "foo"), &n2.UUID, &n2.Body, &n2.AddedOn, &n2.Dirty)
var book core.Book
testutils.MustScan(t, "getting book", db.QueryRow("SELECT dirty FROM books where label = ?", "js"), &book.Dirty)
testutils.AssertEqual(t, book.Dirty, false, "Book dirty mismatch")
testutils.AssertNotEqual(t, n1.UUID, "", "n1 should have UUID")
testutils.AssertEqual(t, n1.Body, "Booleans have toString()", "n1 body mismatch")
testutils.AssertEqual(t, n1.AddedOn, int64(1515199943), "n1 added_on mismatch")
testutils.AssertEqual(t, n1.Dirty, false, "n1 dirty mismatch")
testutils.AssertNotEqual(t, n2.UUID, "", "n2 should have UUID")
testutils.AssertEqual(t, n2.Body, "foo", "n2 body mismatch")
testutils.AssertEqual(t, n2.Dirty, true, "n2 dirty mismatch")
}
func TestEditNote_BodyFlag(t *testing.T) {
// Set up
ctx := testutils.InitEnv(t, "./tmp", "./testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
testutils.Setup4(t, ctx)
// Execute
testutils.RunDnoteCmd(t, ctx, binaryName, "edit", "2", "-c", "foo bar")
// Test
db := ctx.DB
var noteCount, bookCount int
testutils.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
testutils.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
testutils.AssertEqualf(t, bookCount, 1, "book count mismatch")
testutils.AssertEqualf(t, noteCount, 2, "note count mismatch")
var n1, n2 core.Note
testutils.MustScan(t, "getting n1",
db.QueryRow("SELECT uuid, body, added_on, dirty FROM notes where book_uuid = ? AND uuid = ?", "js-book-uuid", "43827b9a-c2b0-4c06-a290-97991c896653"), &n1.UUID, &n1.Body, &n1.AddedOn, &n1.Dirty)
testutils.MustScan(t, "getting n2",
db.QueryRow("SELECT uuid, body, added_on, dirty FROM notes where book_uuid = ? AND uuid = ?", "js-book-uuid", "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f"), &n2.UUID, &n2.Body, &n2.AddedOn, &n2.Dirty)
testutils.AssertEqual(t, n1.UUID, "43827b9a-c2b0-4c06-a290-97991c896653", "n1 should have UUID")
testutils.AssertEqual(t, n1.Body, "Booleans have toString()", "n1 body mismatch")
testutils.AssertEqual(t, n1.Dirty, false, "n1 dirty mismatch")
testutils.AssertEqual(t, n2.UUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "Note should have UUID")
testutils.AssertEqual(t, n2.Body, "foo bar", "Note body mismatch")
testutils.AssertEqual(t, n2.Dirty, true, "n2 dirty mismatch")
testutils.AssertNotEqual(t, n2.EditedOn, 0, "Note edited_on mismatch")
}
func TestRemoveNote(t *testing.T) {
// Set up
ctx := testutils.InitEnv(t, "./tmp", "./testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
testutils.Setup2(t, ctx)
// Execute
testutils.WaitDnoteCmd(t, ctx, testutils.UserConfirm, binaryName, "remove", "js", "1")
// Test
db := ctx.DB
var noteCount, bookCount, jsNoteCount, linuxNoteCount int
testutils.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
testutils.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
testutils.MustScan(t, "counting js notes", db.QueryRow("SELECT count(*) FROM notes WHERE book_uuid = ?", "js-book-uuid"), &jsNoteCount)
testutils.MustScan(t, "counting linux notes", db.QueryRow("SELECT count(*) FROM notes WHERE book_uuid = ?", "linux-book-uuid"), &linuxNoteCount)
testutils.AssertEqualf(t, bookCount, 2, "book count mismatch")
testutils.AssertEqualf(t, noteCount, 3, "note count mismatch")
testutils.AssertEqual(t, jsNoteCount, 2, "js book should have 2 notes")
testutils.AssertEqual(t, linuxNoteCount, 1, "linux book book should have 1 note")
var b1, b2 core.Book
var n1, n2, n3 core.Note
testutils.MustScan(t, "getting b1",
db.QueryRow("SELECT label, deleted, usn FROM books WHERE uuid = ?", "js-book-uuid"),
&b1.Label, &b1.Deleted, &b1.USN)
testutils.MustScan(t, "getting b2",
db.QueryRow("SELECT label, deleted, usn FROM books WHERE uuid = ?", "linux-book-uuid"),
&b2.Label, &b2.Deleted, &b2.USN)
testutils.MustScan(t, "getting n1",
db.QueryRow("SELECT uuid, body, added_on, deleted, dirty, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "js-book-uuid", "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f"),
&n1.UUID, &n1.Body, &n1.AddedOn, &n1.Deleted, &n1.Dirty, &n1.USN)
testutils.MustScan(t, "getting n2",
db.QueryRow("SELECT uuid, body, added_on, deleted, dirty, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "js-book-uuid", "43827b9a-c2b0-4c06-a290-97991c896653"),
&n2.UUID, &n2.Body, &n2.AddedOn, &n2.Deleted, &n2.Dirty, &n2.USN)
testutils.MustScan(t, "getting n3",
db.QueryRow("SELECT uuid, body, added_on, deleted, dirty, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "linux-book-uuid", "3e065d55-6d47-42f2-a6bf-f5844130b2d2"),
&n3.UUID, &n3.Body, &n3.AddedOn, &n3.Deleted, &n3.Dirty, &n3.USN)
testutils.AssertEqual(t, b1.Label, "js", "b1 label mismatch")
testutils.AssertEqual(t, b1.Deleted, false, "b1 deleted mismatch")
testutils.AssertEqual(t, b1.Dirty, false, "b1 Dirty mismatch")
testutils.AssertEqual(t, b1.USN, 111, "b1 usn mismatch")
testutils.AssertEqual(t, b2.Label, "linux", "b2 label mismatch")
testutils.AssertEqual(t, b2.Deleted, false, "b2 deleted mismatch")
testutils.AssertEqual(t, b2.Dirty, false, "b2 Dirty mismatch")
testutils.AssertEqual(t, b2.USN, 122, "b2 usn mismatch")
testutils.AssertEqual(t, n1.UUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "n1 should have UUID")
testutils.AssertEqual(t, n1.Body, "", "n1 body mismatch")
testutils.AssertEqual(t, n1.Deleted, true, "n1 deleted mismatch")
testutils.AssertEqual(t, n1.Dirty, true, "n1 Dirty mismatch")
testutils.AssertEqual(t, n1.USN, 11, "n1 usn mismatch")
testutils.AssertEqual(t, n2.UUID, "43827b9a-c2b0-4c06-a290-97991c896653", "n2 should have UUID")
testutils.AssertEqual(t, n2.Body, "n2 body", "n2 body mismatch")
testutils.AssertEqual(t, n2.Deleted, false, "n2 deleted mismatch")
testutils.AssertEqual(t, n2.Dirty, false, "n2 Dirty mismatch")
testutils.AssertEqual(t, n2.USN, 12, "n2 usn mismatch")
testutils.AssertEqual(t, n3.UUID, "3e065d55-6d47-42f2-a6bf-f5844130b2d2", "n3 should have UUID")
testutils.AssertEqual(t, n3.Body, "n3 body", "n3 body mismatch")
testutils.AssertEqual(t, n3.Deleted, false, "n3 deleted mismatch")
testutils.AssertEqual(t, n3.Dirty, false, "n3 Dirty mismatch")
testutils.AssertEqual(t, n3.USN, 13, "n3 usn mismatch")
}
func TestRemoveBook(t *testing.T) {
// Set up
ctx := testutils.InitEnv(t, "./tmp", "./testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
testutils.Setup2(t, ctx)
// Execute
testutils.WaitDnoteCmd(t, ctx, testutils.UserConfirm, binaryName, "remove", "-b", "js")
// Test
db := ctx.DB
var noteCount, bookCount, jsNoteCount, linuxNoteCount int
testutils.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
testutils.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
testutils.MustScan(t, "counting js notes", db.QueryRow("SELECT count(*) FROM notes WHERE book_uuid = ?", "js-book-uuid"), &jsNoteCount)
testutils.MustScan(t, "counting linux notes", db.QueryRow("SELECT count(*) FROM notes WHERE book_uuid = ?", "linux-book-uuid"), &linuxNoteCount)
testutils.AssertEqualf(t, bookCount, 2, "book count mismatch")
testutils.AssertEqualf(t, noteCount, 3, "note count mismatch")
testutils.AssertEqual(t, jsNoteCount, 2, "js book should have 2 notes")
testutils.AssertEqual(t, linuxNoteCount, 1, "linux book book should have 1 note")
var b1, b2 core.Book
var n1, n2, n3 core.Note
testutils.MustScan(t, "getting b1",
db.QueryRow("SELECT label, dirty, deleted, usn FROM books WHERE uuid = ?", "js-book-uuid"),
&b1.Label, &b1.Dirty, &b1.Deleted, &b1.USN)
testutils.MustScan(t, "getting b2",
db.QueryRow("SELECT label, dirty, deleted, usn FROM books WHERE uuid = ?", "linux-book-uuid"),
&b2.Label, &b2.Dirty, &b2.Deleted, &b2.USN)
testutils.MustScan(t, "getting n1",
db.QueryRow("SELECT uuid, body, added_on, dirty, deleted, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "js-book-uuid", "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f"),
&n1.UUID, &n1.Body, &n1.AddedOn, &n1.Deleted, &n1.Dirty, &n1.USN)
testutils.MustScan(t, "getting n2",
db.QueryRow("SELECT uuid, body, added_on, dirty, deleted, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "js-book-uuid", "43827b9a-c2b0-4c06-a290-97991c896653"),
&n2.UUID, &n2.Body, &n2.AddedOn, &n2.Deleted, &n2.Dirty, &n2.USN)
testutils.MustScan(t, "getting n3",
db.QueryRow("SELECT uuid, body, added_on, dirty, deleted, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "linux-book-uuid", "3e065d55-6d47-42f2-a6bf-f5844130b2d2"),
&n3.UUID, &n3.Body, &n3.AddedOn, &n3.Deleted, &n3.Dirty, &n3.USN)
testutils.AssertNotEqual(t, b1.Label, "js", "b1 label mismatch")
testutils.AssertEqual(t, b1.Dirty, true, "b1 Dirty mismatch")
testutils.AssertEqual(t, b1.Deleted, true, "b1 deleted mismatch")
testutils.AssertEqual(t, b1.USN, 111, "b1 usn mismatch")
testutils.AssertEqual(t, b2.Label, "linux", "b2 label mismatch")
testutils.AssertEqual(t, b2.Dirty, false, "b2 Dirty mismatch")
testutils.AssertEqual(t, b2.Deleted, false, "b2 deleted mismatch")
testutils.AssertEqual(t, b2.USN, 122, "b2 usn mismatch")
testutils.AssertEqual(t, n1.UUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "n1 should have UUID")
testutils.AssertEqual(t, n1.Body, "", "n1 body mismatch")
testutils.AssertEqual(t, n1.Dirty, true, "n1 Dirty mismatch")
testutils.AssertEqual(t, n1.Deleted, true, "n1 deleted mismatch")
testutils.AssertEqual(t, n1.USN, 11, "n1 usn mismatch")
testutils.AssertEqual(t, n2.UUID, "43827b9a-c2b0-4c06-a290-97991c896653", "n2 should have UUID")
testutils.AssertEqual(t, n2.Body, "", "n2 body mismatch")
testutils.AssertEqual(t, n2.Dirty, true, "n2 Dirty mismatch")
testutils.AssertEqual(t, n2.Deleted, true, "n2 deleted mismatch")
testutils.AssertEqual(t, n2.USN, 12, "n2 usn mismatch")
testutils.AssertEqual(t, n3.UUID, "3e065d55-6d47-42f2-a6bf-f5844130b2d2", "n3 should have UUID")
testutils.AssertEqual(t, n3.Body, "n3 body", "n3 body mismatch")
testutils.AssertEqual(t, n3.Dirty, false, "n3 Dirty mismatch")
testutils.AssertEqual(t, n3.Deleted, false, "n3 deleted mismatch")
testutils.AssertEqual(t, n3.USN, 13, "n3 usn mismatch")
}

File diff suppressed because it is too large Load diff

View file

@ -1,50 +0,0 @@
CREATE TABLE books
(
uuid text PRIMARY KEY,
label text NOT NULL
, dirty bool DEFAULT false, usn int DEFAULT 0 NOT NULL, deleted bool DEFAULT false);
CREATE TABLE system
(
key string NOT NULL,
value text NOT NULL
);
CREATE UNIQUE INDEX idx_books_label ON books(label);
CREATE UNIQUE INDEX idx_books_uuid ON books(uuid);
CREATE TABLE IF NOT EXISTS "notes"
(
uuid text NOT NULL,
book_uuid text NOT NULL,
body text NOT NULL,
added_on integer NOT NULL,
edited_on integer DEFAULT 0,
public bool DEFAULT false,
dirty bool DEFAULT false,
usn int DEFAULT 0 NOT NULL,
deleted bool DEFAULT false
);
CREATE VIRTUAL TABLE note_fts USING fts5(content=notes, body, tokenize="porter unicode61 categories 'L* N* Co Ps Pe'")
/* note_fts(body) */;
CREATE TABLE IF NOT EXISTS 'note_fts_data'(id INTEGER PRIMARY KEY, block BLOB);
CREATE TABLE IF NOT EXISTS 'note_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS 'note_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB);
CREATE TABLE IF NOT EXISTS 'note_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID;
CREATE TRIGGER notes_after_insert AFTER INSERT ON notes BEGIN
INSERT INTO note_fts(rowid, body) VALUES (new.rowid, new.body);
END;
CREATE TRIGGER notes_after_delete AFTER DELETE ON notes BEGIN
INSERT INTO note_fts(note_fts, rowid, body) VALUES ('delete', old.rowid, old.body);
END;
CREATE TRIGGER notes_after_update AFTER UPDATE ON notes BEGIN
INSERT INTO note_fts(note_fts, rowid, body) VALUES ('delete', old.rowid, old.body);
INSERT INTO note_fts(rowid, body) VALUES (new.rowid, new.body);
END;
CREATE TABLE actions
(
uuid text PRIMARY KEY,
schema integer NOT NULL,
type text NOT NULL,
data text NOT NULL,
timestamp integer NOT NULL
);
CREATE UNIQUE INDEX idx_notes_uuid ON notes(uuid);
CREATE INDEX idx_notes_book_uuid ON notes(book_uuid);

View file

@ -1,363 +0,0 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
// Package testutils provides utilities used in tests
package testutils
import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/utils"
"github.com/pkg/errors"
)
// InitEnv sets up a test env and returns a new dnote context
func InitEnv(t *testing.T, dnotehomePath string, fixturePath string, migrated bool) infra.DnoteCtx {
os.Setenv("DNOTE_HOME_DIR", dnotehomePath)
ctx, err := infra.NewCtx("", "")
if err != nil {
t.Fatal(errors.Wrap(err, "getting new ctx"))
}
// set up directory
if err := os.MkdirAll(ctx.DnoteDir, 0755); err != nil {
t.Fatal(err)
}
// set up db
b := ReadFileAbs(fixturePath)
setupSQL := string(b)
db := ctx.DB
if _, err := db.Exec(setupSQL); err != nil {
t.Fatal(errors.Wrap(err, "running schema sql"))
}
if migrated {
// mark migrations as done. When adding new migrations, bump the numbers here.
if _, err := db.Exec("INSERT INTO system (key, value) VALUES (? , ?);", infra.SystemSchema, 11); err != nil {
t.Fatal(errors.Wrap(err, "inserting schema"))
}
if _, err := db.Exec("INSERT INTO system (key, value) VALUES (? , ?);", infra.SystemRemoteSchema, 1); err != nil {
t.Fatal(errors.Wrap(err, "inserting remote schema"))
}
}
return ctx
}
// Login simulates a logged in user by inserting credentials in the local database
func Login(t *testing.T, ctx *infra.DnoteCtx) {
db := ctx.DB
MustExec(t, "inserting sessionKey", db, "INSERT INTO system (key, value) VALUES (?, ?)", infra.SystemSessionKey, "someSessionKey")
MustExec(t, "inserting sessionKeyExpiry", db, "INSERT INTO system (key, value) VALUES (?, ?)", infra.SystemSessionKeyExpiry, time.Now().Add(24*time.Hour).Unix())
MustExec(t, "inserting cipherKey", db, "INSERT INTO system (key, value) VALUES (?, ?)", infra.SystemCipherKey, "QUVTMjU2S2V5LTMyQ2hhcmFjdGVyczEyMzQ1Njc4OTA=")
ctx.SessionKey = "someSessionKey"
ctx.SessionKeyExpiry = time.Now().Add(24 * time.Hour).Unix()
ctx.CipherKey = []byte("AES256Key-32Characters1234567890")
}
// TeardownEnv cleans up the test env represented by the given context
func TeardownEnv(ctx infra.DnoteCtx) {
ctx.DB.Close()
if err := os.RemoveAll(ctx.DnoteDir); err != nil {
panic(err)
}
}
// CopyFixture writes the content of the given fixture to the filename inside the dnote dir
func CopyFixture(ctx infra.DnoteCtx, fixturePath string, filename string) {
fp, err := filepath.Abs(fixturePath)
if err != nil {
panic(err)
}
dp, err := filepath.Abs(filepath.Join(ctx.DnoteDir, filename))
if err != nil {
panic(err)
}
err = utils.CopyFile(fp, dp)
if err != nil {
panic(err)
}
}
// WriteFile writes a file with the given content and filename inside the dnote dir
func WriteFile(ctx infra.DnoteCtx, content []byte, filename string) {
dp, err := filepath.Abs(filepath.Join(ctx.DnoteDir, filename))
if err != nil {
panic(err)
}
if err := ioutil.WriteFile(dp, content, 0644); err != nil {
panic(err)
}
}
// ReadFile reads the content of the file with the given name in dnote dir
func ReadFile(ctx infra.DnoteCtx, filename string) []byte {
path := filepath.Join(ctx.DnoteDir, filename)
b, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
return b
}
// ReadFileAbs reads the content of the file with the given file path by resolving
// it as an absolute path
func ReadFileAbs(relpath string) []byte {
fp, err := filepath.Abs(relpath)
if err != nil {
panic(err)
}
b, err := ioutil.ReadFile(fp)
if err != nil {
panic(err)
}
return b
}
func checkEqual(a interface{}, b interface{}, message string) (bool, string) {
if a == b {
return true, ""
}
var m string
if len(message) == 0 {
m = fmt.Sprintf("%v != %v", a, b)
} else {
m = message
}
errorMessage := fmt.Sprintf("%s.\n==== Actual ====\n%+v\n=============\n==== Expected ====\n%+v\n=================", m, a, b)
return false, errorMessage
}
// AssertEqual errors a test if the actual does not match the expected
func AssertEqual(t *testing.T, a interface{}, b interface{}, message string) {
ok, m := checkEqual(a, b, message)
if !ok {
t.Error(m)
}
}
// AssertEqualf fails a test if the actual does not match the expected
func AssertEqualf(t *testing.T, a interface{}, b interface{}, message string) {
ok, m := checkEqual(a, b, message)
if !ok {
t.Fatal(m)
}
}
// AssertNotEqual fails a test if the actual matches the expected
func AssertNotEqual(t *testing.T, a interface{}, b interface{}, message string) {
if a != b {
return
}
if len(message) == 0 {
message = fmt.Sprintf("%v == %v", a, b)
}
t.Errorf("%s. Actual: %+v. Expected: %+v.", message, a, b)
}
// AssertDeepEqual fails a test if the actual does not deeply equal the expected
func AssertDeepEqual(t *testing.T, a interface{}, b interface{}, message string) {
if reflect.DeepEqual(a, b) {
return
}
if len(message) == 0 {
message = fmt.Sprintf("%v != %v", a, b)
}
t.Errorf("%s.\nActual: %+v.\nExpected: %+v.", message, a, b)
}
// ReadJSON reads JSON fixture to the struct at the destination address
func ReadJSON(path string, destination interface{}) {
var dat []byte
dat, err := ioutil.ReadFile(path)
if err != nil {
panic(errors.Wrap(err, "Failed to load fixture payload"))
}
if err := json.Unmarshal(dat, destination); err != nil {
panic(errors.Wrap(err, "Failed to get event"))
}
}
// IsEqualJSON deeply compares two JSON byte slices
func IsEqualJSON(s1, s2 []byte) (bool, error) {
var o1 interface{}
var o2 interface{}
if err := json.Unmarshal(s1, &o1); err != nil {
return false, errors.Wrap(err, "unmarshalling first JSON")
}
if err := json.Unmarshal(s2, &o2); err != nil {
return false, errors.Wrap(err, "unmarshalling second JSON")
}
return reflect.DeepEqual(o1, o2), nil
}
// MustExec executes the given SQL query and fails a test if an error occurs
func MustExec(t *testing.T, message string, db *infra.DB, query string, args ...interface{}) sql.Result {
result, err := db.Exec(query, args...)
if err != nil {
t.Fatal(errors.Wrap(errors.Wrap(err, "executing sql"), message))
}
return result
}
// MustScan scans the given row and fails a test in case of any errors
func MustScan(t *testing.T, message string, row *sql.Row, args ...interface{}) {
err := row.Scan(args...)
if err != nil {
t.Fatal(errors.Wrap(errors.Wrap(err, "scanning a row"), message))
}
}
// NewDnoteCmd returns a new Dnote command and a pointer to stderr
func NewDnoteCmd(ctx infra.DnoteCtx, binaryName string, arg ...string) (*exec.Cmd, *bytes.Buffer, *bytes.Buffer, error) {
var stderr, stdout bytes.Buffer
binaryPath, err := filepath.Abs(binaryName)
if err != nil {
return &exec.Cmd{}, &stderr, &stdout, errors.Wrap(err, "getting the absolute path to the test binary")
}
cmd := exec.Command(binaryPath, arg...)
cmd.Env = []string{fmt.Sprintf("DNOTE_DIR=%s", ctx.DnoteDir), fmt.Sprintf("DNOTE_HOME_DIR=%s", ctx.HomeDir)}
cmd.Stderr = &stderr
cmd.Stdout = &stdout
return cmd, &stderr, &stdout, nil
}
// RunDnoteCmd runs a dnote command
func RunDnoteCmd(t *testing.T, ctx infra.DnoteCtx, binaryName string, arg ...string) {
t.Logf("running: %s %s", binaryName, strings.Join(arg, " "))
cmd, stderr, stdout, err := NewDnoteCmd(ctx, binaryName, arg...)
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrap(err, "getting command").Error())
}
cmd.Env = append(cmd.Env, "DNOTE_DEBUG=1")
if err := cmd.Run(); err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrapf(err, "running command %s", stderr.String()))
}
// Print stdout if and only if test fails later
t.Logf("\n%s", stdout)
}
// WaitDnoteCmd runs a dnote command and waits until the command is exited
func WaitDnoteCmd(t *testing.T, ctx infra.DnoteCtx, runFunc func(io.WriteCloser) error, binaryName string, arg ...string) {
t.Logf("running: %s %s", binaryName, strings.Join(arg, " "))
cmd, stderr, stdout, err := NewDnoteCmd(ctx, binaryName, arg...)
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrap(err, "getting command").Error())
}
stdin, err := cmd.StdinPipe()
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrap(err, "getting stdin %s"))
}
defer stdin.Close()
// Start the program
err = cmd.Start()
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrap(err, "starting command"))
}
err = runFunc(stdin)
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrap(err, "running with stdin"))
}
err = cmd.Wait()
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrapf(err, "running command %s", stderr.String()))
}
// Print stdout if and only if test fails later
t.Logf("\n%s", stdout)
}
// UserConfirm simulates confirmation from the user by writing to stdin
func UserConfirm(stdin io.WriteCloser) error {
// confirm
if _, err := io.WriteString(stdin, "y\n"); err != nil {
return errors.Wrap(err, "indicating confirmation in stdin")
}
return nil
}
// MustMarshalJSON marshalls the given interface into JSON.
// If there is any error, it fails the test.
func MustMarshalJSON(t *testing.T, v interface{}) []byte {
b, err := json.Marshal(v)
if err != nil {
t.Fatalf("%s: marshalling data", t.Name())
}
return b
}
// MustUnmarshalJSON marshalls the given interface into JSON.
// If there is any error, it fails the test.
func MustUnmarshalJSON(t *testing.T, data []byte, v interface{}) {
err := json.Unmarshal(data, v)
if err != nil {
t.Fatalf("%s: unmarshalling data", t.Name())
}
}

View file

@ -1,79 +0,0 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package testutils
import (
"github.com/dnote/dnote/cli/infra"
"testing"
)
// Setup1 sets up a dnote env #1
// dnote4.json
func Setup1(t *testing.T, ctx infra.DnoteCtx) {
db := ctx.DB
b1UUID := "js-book-uuid"
b2UUID := "linux-book-uuid"
MustExec(t, "setting up book 1", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "js")
MustExec(t, "setting up book 2", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b2UUID, "linux")
MustExec(t, "setting up note 1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "43827b9a-c2b0-4c06-a290-97991c896653", b1UUID, "Booleans have toString()", 1515199943)
}
// Setup2 sets up a dnote env #2
// dnote3.json
func Setup2(t *testing.T, ctx infra.DnoteCtx) {
db := ctx.DB
b1UUID := "js-book-uuid"
b2UUID := "linux-book-uuid"
MustExec(t, "setting up book 1", db, "INSERT INTO books (uuid, label, usn) VALUES (?, ?, ?)", b1UUID, "js", 111)
MustExec(t, "setting up book 2", db, "INSERT INTO books (uuid, label, usn) VALUES (?, ?, ?)", b2UUID, "linux", 122)
MustExec(t, "setting up note 1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, usn) VALUES (?, ?, ?, ?, ?)", "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", b1UUID, "n1 body", 1515199951, 11)
MustExec(t, "setting up note 2", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, usn) VALUES (?, ?, ?, ?, ?)", "43827b9a-c2b0-4c06-a290-97991c896653", b1UUID, "n2 body", 1515199943, 12)
MustExec(t, "setting up note 3", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, usn) VALUES (?, ?, ?, ?, ?)", "3e065d55-6d47-42f2-a6bf-f5844130b2d2", b2UUID, "n3 body", 1515199961, 13)
}
// Setup3 sets up a dnote env #1
// dnote1.json
func Setup3(t *testing.T, ctx infra.DnoteCtx) {
db := ctx.DB
b1UUID := "js-book-uuid"
MustExec(t, "setting up book 1", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "js")
MustExec(t, "setting up note 1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "43827b9a-c2b0-4c06-a290-97991c896653", b1UUID, "Booleans have toString()", 1515199943)
}
// Setup4 sets up a dnote env #1
// dnote2.json
func Setup4(t *testing.T, ctx infra.DnoteCtx) {
db := ctx.DB
b1UUID := "js-book-uuid"
MustExec(t, "setting up book 1", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "js")
MustExec(t, "setting up note 1", db, "INSERT INTO notes (rowid, uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?, ?)", 1, "43827b9a-c2b0-4c06-a290-97991c896653", b1UUID, "Booleans have toString()", 1515199943)
MustExec(t, "setting up note 2", db, "INSERT INTO notes (rowid, uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?, ?)", 2, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", b1UUID, "Date object implements mathematical comparisons", 1515199951)
}

123
pkg/assert/assert.go Normal file
View file

@ -0,0 +1,123 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
// Package assert provides functions to assert a condition in tests
package assert
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"testing"
"github.com/pkg/errors"
)
func checkEqual(a interface{}, b interface{}, message string) (bool, string) {
if a == b {
return true, ""
}
var m string
if len(message) == 0 {
m = fmt.Sprintf("%v != %v", a, b)
} else {
m = message
}
errorMessage := fmt.Sprintf("%s.\n==== Actual ====\n%+v\n=============\n==== Expected ====\n%+v\n=================", m, a, b)
return false, errorMessage
}
// Equal errors a test if the actual does not match the expected
func Equal(t *testing.T, a interface{}, b interface{}, message string) {
ok, m := checkEqual(a, b, message)
if !ok {
t.Error(m)
}
}
// Equalf fails a test if the actual does not match the expected
func Equalf(t *testing.T, a interface{}, b interface{}, message string) {
ok, m := checkEqual(a, b, message)
if !ok {
t.Fatal(m)
}
}
// NotEqual fails a test if the actual matches the expected
func NotEqual(t *testing.T, a interface{}, b interface{}, message string) {
if a != b {
return
}
if len(message) == 0 {
message = fmt.Sprintf("%v == %v", a, b)
}
t.Errorf("%s. Actual: %+v. Expected: %+v.", message, a, b)
}
// DeepEqual fails a test if the actual does not deeply equal the expected
func DeepEqual(t *testing.T, a interface{}, b interface{}, message string) {
if reflect.DeepEqual(a, b) {
return
}
if len(message) == 0 {
message = fmt.Sprintf("%v != %v", a, b)
}
t.Errorf("%s.\nActual: %+v.\nExpected: %+v.", message, a, b)
}
// EqualJSON asserts that two JSON strings are equal
func EqualJSON(t *testing.T, a, b, message string) {
var o1 interface{}
var o2 interface{}
err := json.Unmarshal([]byte(a), &o1)
if err != nil {
panic(fmt.Errorf("Error mashalling string 1 :: %s", err.Error()))
}
err = json.Unmarshal([]byte(b), &o2)
if err != nil {
panic(fmt.Errorf("Error mashalling string 2 :: %s", err.Error()))
}
if reflect.DeepEqual(o1, o2) {
return
}
if len(message) == 0 {
message = fmt.Sprintf("%v != %v", a, b)
}
t.Errorf("%s.\nActual: %+v.\nExpected: %+v.", message, a, b)
}
// StatusCodeEquals asserts that the reponse's status code is equal to the
// expected
func StatusCodeEquals(t *testing.T, res *http.Response, expected int, message string) {
if res.StatusCode != expected {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(errors.Wrap(err, "reading body"))
}
t.Errorf("status code mismatch. %s: got %v want %v. Message was: '%s'", message, res.StatusCode, expected, string(body))
}
}

View file

@ -17,6 +17,14 @@
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
version = "v1.7.0"
[[projects]]
branch = "endpoint-186"
digest = "1:b56e65cab256427453f4454af3e7be2c1c544b52332772dbf7e6992aa7fdf599"
name = "github.com/dnote/dnote"
packages = ["pkg/assert"]
pruneopts = ""
revision = "54646c6bf85fba5efb8b4de67c9fed19cdbe0d97"
[[projects]]
branch = "master"
digest = "1:fe99ddb68e996f2f9f7995e9765bc283ceef12dbe30de17922900c1cfa9dfc09"
@ -81,6 +89,14 @@
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
version = "v1.2.0"
[[projects]]
digest = "1:3962f553b77bf6c03fc07cd687a22dd3b00fe11aa14d31194f5505f5bb65cdc8"
name = "github.com/sergi/go-diff"
packages = ["diffmatchpatch"]
pruneopts = ""
revision = "1744e2970ca51c86172c8190fadad617561ed6e7"
version = "v1.0.0"
[[projects]]
digest = "1:78715f4ed019d19795e67eed1dc63f525461d925616b1ed02b72582c01362440"
name = "github.com/spf13/cobra"
@ -134,10 +150,12 @@
input-imports = [
"github.com/dnote/actions",
"github.com/dnote/color",
"github.com/dnote/dnote/pkg/assert",
"github.com/google/go-github/github",
"github.com/mattn/go-sqlite3",
"github.com/pkg/errors",
"github.com/satori/go.uuid",
"github.com/sergi/go-diff/diffmatchpatch",
"github.com/spf13/cobra",
"golang.org/x/crypto/hkdf",
"golang.org/x/crypto/pbkdf2",

View file

@ -30,8 +30,8 @@ import (
"strings"
"time"
"github.com/dnote/dnote/cli/crypt"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/crypt"
"github.com/pkg/errors"
)
@ -43,7 +43,7 @@ type requestOptions struct {
HTTPClient *http.Client
}
func getReq(ctx infra.DnoteCtx, path, method, body string) (*http.Request, error) {
func getReq(ctx context.DnoteCtx, path, method, body string) (*http.Request, error) {
endpoint := fmt.Sprintf("%s%s", ctx.APIEndpoint, path)
req, err := http.NewRequest(method, endpoint, strings.NewReader(body))
if err != nil {
@ -77,7 +77,7 @@ func checkRespErr(res *http.Response) error {
}
// doReq does a http request to the given path in the api endpoint
func doReq(ctx infra.DnoteCtx, method, path, body string, options *requestOptions) (*http.Response, error) {
func doReq(ctx context.DnoteCtx, method, path, body string, options *requestOptions) (*http.Response, error) {
req, err := getReq(ctx, path, method, body)
if err != nil {
return nil, errors.Wrap(err, "getting request")
@ -104,7 +104,7 @@ func doReq(ctx infra.DnoteCtx, method, path, body string, options *requestOption
// doAuthorizedReq does a http request to the given path in the api endpoint as a user,
// with the appropriate headers. The given path should include the preceding slash.
func doAuthorizedReq(ctx infra.DnoteCtx, method, path, body string, options *requestOptions) (*http.Response, error) {
func doAuthorizedReq(ctx context.DnoteCtx, method, path, body string, options *requestOptions) (*http.Response, error) {
if ctx.SessionKey == "" {
return nil, errors.New("no session key found")
}
@ -120,7 +120,7 @@ type GetSyncStateResp struct {
}
// GetSyncState gets the sync state response from the server
func GetSyncState(ctx infra.DnoteCtx) (GetSyncStateResp, error) {
func GetSyncState(ctx context.DnoteCtx) (GetSyncStateResp, error) {
var ret GetSyncStateResp
res, err := doAuthorizedReq(ctx, "GET", "/v1/sync/state", "", nil)
@ -184,7 +184,7 @@ type GetSyncFragmentResp struct {
}
// GetSyncFragment gets a sync fragment response from the server
func GetSyncFragment(ctx infra.DnoteCtx, afterUSN int) (GetSyncFragmentResp, error) {
func GetSyncFragment(ctx context.DnoteCtx, afterUSN int) (GetSyncFragmentResp, error) {
v := url.Values{}
v.Set("after_usn", strconv.Itoa(afterUSN))
queryStr := v.Encode()
@ -226,7 +226,7 @@ type CreateBookResp struct {
}
// CreateBook creates a new book in the server
func CreateBook(ctx infra.DnoteCtx, label string) (CreateBookResp, error) {
func CreateBook(ctx context.DnoteCtx, label string) (CreateBookResp, error) {
encLabel, err := crypt.AesGcmEncrypt(ctx.CipherKey, []byte(label))
if err != nil {
return CreateBookResp{}, errors.Wrap(err, "encrypting the label")
@ -263,7 +263,7 @@ type UpdateBookResp struct {
}
// UpdateBook updates a book in the server
func UpdateBook(ctx infra.DnoteCtx, label, uuid string) (UpdateBookResp, error) {
func UpdateBook(ctx context.DnoteCtx, label, uuid string) (UpdateBookResp, error) {
encName, err := crypt.AesGcmEncrypt(ctx.CipherKey, []byte(label))
if err != nil {
return UpdateBookResp{}, errors.Wrap(err, "encrypting the content")
@ -298,7 +298,7 @@ type DeleteBookResp struct {
}
// DeleteBook deletes a book in the server
func DeleteBook(ctx infra.DnoteCtx, uuid string) (DeleteBookResp, error) {
func DeleteBook(ctx context.DnoteCtx, uuid string) (DeleteBookResp, error) {
endpoint := fmt.Sprintf("/v1/books/%s", uuid)
res, err := doAuthorizedReq(ctx, "DELETE", endpoint, "", nil)
if err != nil {
@ -347,7 +347,7 @@ type RespNote struct {
}
// CreateNote creates a note in the server
func CreateNote(ctx infra.DnoteCtx, bookUUID, content string) (CreateNoteResp, error) {
func CreateNote(ctx context.DnoteCtx, bookUUID, content string) (CreateNoteResp, error) {
encBody, err := crypt.AesGcmEncrypt(ctx.CipherKey, []byte(content))
if err != nil {
return CreateNoteResp{}, errors.Wrap(err, "encrypting the content")
@ -388,7 +388,7 @@ type UpdateNoteResp struct {
}
// UpdateNote updates a note in the server
func UpdateNote(ctx infra.DnoteCtx, uuid, bookUUID, content string, public bool) (UpdateNoteResp, error) {
func UpdateNote(ctx context.DnoteCtx, uuid, bookUUID, content string, public bool) (UpdateNoteResp, error) {
encBody, err := crypt.AesGcmEncrypt(ctx.CipherKey, []byte(content))
if err != nil {
return UpdateNoteResp{}, errors.Wrap(err, "encrypting the content")
@ -425,7 +425,7 @@ type DeleteNoteResp struct {
}
// DeleteNote removes a note in the server
func DeleteNote(ctx infra.DnoteCtx, uuid string) (DeleteNoteResp, error) {
func DeleteNote(ctx context.DnoteCtx, uuid string) (DeleteNoteResp, error) {
endpoint := fmt.Sprintf("/v1/notes/%s", uuid)
res, err := doAuthorizedReq(ctx, "DELETE", endpoint, "", nil)
if err != nil {
@ -447,7 +447,7 @@ type GetBooksResp []struct {
}
// GetBooks gets books from the server
func GetBooks(ctx infra.DnoteCtx, sessionKey string) (GetBooksResp, error) {
func GetBooks(ctx context.DnoteCtx, sessionKey string) (GetBooksResp, error) {
res, err := doAuthorizedReq(ctx, "GET", "/v1/books", "", nil)
if err != nil {
return GetBooksResp{}, errors.Wrap(err, "making http request")
@ -467,7 +467,7 @@ type PresigninResponse struct {
}
// GetPresignin gets presignin credentials
func GetPresignin(ctx infra.DnoteCtx, email string) (PresigninResponse, error) {
func GetPresignin(ctx context.DnoteCtx, email string) (PresigninResponse, error) {
res, err := doReq(ctx, "GET", fmt.Sprintf("/v1/presignin?email=%s", email), "", nil)
if err != nil {
return PresigninResponse{}, errors.Wrap(err, "making http request")
@ -495,7 +495,7 @@ type SigninResponse struct {
}
// Signin requests a session token
func Signin(ctx infra.DnoteCtx, email, authKey string) (SigninResponse, error) {
func Signin(ctx context.DnoteCtx, email, authKey string) (SigninResponse, error) {
payload := SigninPayload{
Email: email,
AuthKey: authKey,
@ -522,7 +522,7 @@ func Signin(ctx infra.DnoteCtx, email, authKey string) (SigninResponse, error) {
}
// Signout deletes a user session on the server side
func Signout(ctx infra.DnoteCtx, sessionKey string) error {
func Signout(ctx context.DnoteCtx, sessionKey string) error {
hc := http.Client{
// No need to follow redirect
CheckRedirect: func(req *http.Request, via []*http.Request) error {

View file

@ -23,10 +23,14 @@ import (
"strings"
"time"
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/cli/utils"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/dnote/dnote/pkg/cli/output"
"github.com/dnote/dnote/pkg/cli/ui"
"github.com/dnote/dnote/pkg/cli/upgrade"
"github.com/dnote/dnote/pkg/cli/utils"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -51,7 +55,7 @@ func preRun(cmd *cobra.Command, args []string) error {
}
// NewCmd returns a new add command
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func NewCmd(ctx context.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
Use: "add <book>",
Short: "Add a new note",
@ -102,7 +106,7 @@ func validateBookName(name string) error {
return nil
}
func newRun(ctx infra.DnoteCtx) core.RunEFunc {
func newRun(ctx context.DnoteCtx) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
bookName := args[0]
@ -111,8 +115,8 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
}
if content == "" {
fpath := core.GetDnoteTmpContentPath(ctx)
err := core.GetEditorInput(ctx, fpath, &content)
fpath := ui.GetTmpContentPath(ctx)
err := ui.GetEditorInput(ctx, fpath, &content)
if err != nil {
return errors.Wrap(err, "Failed to get editor input")
}
@ -130,14 +134,15 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
log.Successf("added to %s\n", bookName)
info, err := core.GetNoteInfo(ctx, noteRowID)
db := ctx.DB
info, err := database.GetNoteInfo(db, noteRowID)
if err != nil {
return err
}
core.PrintNoteInfo(info)
output.NoteInfo(info)
if err := core.CheckUpdate(ctx); err != nil {
if err := upgrade.Check(ctx); err != nil {
log.Error(errors.Wrap(err, "automatically checking updates").Error())
}
@ -145,7 +150,7 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
}
}
func writeNote(ctx infra.DnoteCtx, bookLabel string, content string, ts int64) (string, error) {
func writeNote(ctx context.DnoteCtx, bookLabel string, content string, ts int64) (string, error) {
tx, err := ctx.DB.Begin()
if err != nil {
return "", errors.Wrap(err, "beginning a transaction")
@ -156,7 +161,7 @@ func writeNote(ctx infra.DnoteCtx, bookLabel string, content string, ts int64) (
if err == sql.ErrNoRows {
bookUUID = utils.GenerateUUID()
b := core.NewBook(bookUUID, bookLabel, 0, false, true)
b := database.NewBook(bookUUID, bookLabel, 0, false, true)
err = b.Insert(tx)
if err != nil {
tx.Rollback()
@ -167,7 +172,7 @@ func writeNote(ctx infra.DnoteCtx, bookLabel string, content string, ts int64) (
}
noteUUID := utils.GenerateUUID()
n := core.NewNote(noteUUID, bookUUID, content, ts, 0, 0, false, false, true)
n := database.NewNote(noteUUID, bookUUID, content, ts, 0, 0, false, false, true)
err = n.Insert(tx)
if err != nil {

View file

@ -22,7 +22,7 @@ import (
"fmt"
"testing"
"github.com/dnote/dnote/cli/testutils"
"github.com/dnote/dnote/pkg/assert"
)
func TestValidateBookName(t *testing.T) {
@ -101,6 +101,6 @@ func TestValidateBookName(t *testing.T) {
for _, tc := range testCases {
actual := validateBookName(tc.input)
testutils.AssertEqual(t, actual, tc.expected, fmt.Sprintf("result does not match for the input '%s'", tc.input))
assert.Equal(t, actual, tc.expected, fmt.Sprintf("result does not match for the input '%s'", tc.input))
}
}

View file

@ -19,9 +19,11 @@
package cat
import (
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/dnote/dnote/pkg/cli/output"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -45,7 +47,7 @@ func preRun(cmd *cobra.Command, args []string) error {
}
// NewCmd returns a new cat command
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func NewCmd(ctx context.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
Use: "cat <book name> <note index>",
Aliases: []string{"c"},
@ -60,7 +62,7 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
}
// NewRun returns a new run function
func NewRun(ctx infra.DnoteCtx) core.RunEFunc {
func NewRun(ctx context.DnoteCtx) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
var noteRowID string
@ -72,12 +74,13 @@ func NewRun(ctx infra.DnoteCtx) core.RunEFunc {
noteRowID = args[0]
}
info, err := core.GetNoteInfo(ctx, noteRowID)
db := ctx.DB
info, err := database.GetNoteInfo(db, noteRowID)
if err != nil {
return err
}
core.PrintNoteInfo(info)
output.NoteInfo(info)
return nil
}

View file

@ -24,9 +24,10 @@ import (
"io/ioutil"
"time"
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/dnote/dnote/pkg/cli/ui"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -41,7 +42,7 @@ var example = `
dnote edit 3 -c "new content"`
// NewCmd returns a new edit command
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func NewCmd(ctx context.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
Use: "edit",
Short: "Edit a note",
@ -65,7 +66,7 @@ func preRun(cmd *cobra.Command, args []string) error {
return nil
}
func newRun(ctx infra.DnoteCtx) core.RunEFunc {
func newRun(ctx context.DnoteCtx) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
db := ctx.DB
@ -88,14 +89,14 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
}
if newContent == "" {
fpath := core.GetDnoteTmpContentPath(ctx)
fpath := ui.GetTmpContentPath(ctx)
e := ioutil.WriteFile(fpath, []byte(oldContent), 0644)
if e != nil {
return errors.Wrap(e, "preparing tmp content file")
}
e = core.GetEditorInput(ctx, fpath, &newContent)
e = ui.GetEditorInput(ctx, fpath, &newContent)
if e != nil {
return errors.Wrap(err, "getting editor input")
}
@ -106,7 +107,7 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
}
ts := time.Now().UnixNano()
newContent = core.SanitizeContent(newContent)
newContent = ui.SanitizeContent(newContent)
tx, err := db.Begin()
if err != nil {

View file

@ -23,9 +23,9 @@ import (
"fmt"
"strings"
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -52,7 +52,7 @@ func preRun(cmd *cobra.Command, args []string) error {
}
// NewCmd returns a new remove command
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func NewCmd(ctx context.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
Use: "find",
Short: "Find notes by keywords",
@ -130,7 +130,7 @@ func escapePhrase(s string) (string, error) {
return b.String(), nil
}
func doQuery(ctx infra.DnoteCtx, query, bookName string) (*sql.Rows, error) {
func doQuery(ctx context.DnoteCtx, query, bookName string) (*sql.Rows, error) {
db := ctx.DB
sql := `SELECT
@ -153,7 +153,7 @@ func doQuery(ctx infra.DnoteCtx, query, bookName string) (*sql.Rows, error) {
return rows, err
}
func newRun(ctx infra.DnoteCtx) core.RunEFunc {
func newRun(ctx context.DnoteCtx) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
phrase, err := escapePhrase(args[0])
if err != nil {

View file

@ -22,7 +22,7 @@ import (
"fmt"
"testing"
"github.com/dnote/dnote/cli/testutils"
"github.com/dnote/dnote/pkg/assert"
)
func TestScanToken(t *testing.T) {
@ -117,8 +117,8 @@ func TestScanToken(t *testing.T) {
t.Run(fmt.Sprintf("test case %d", tcIdx), func(t *testing.T) {
tok, nextIdx := scanToken(tc.idx, tc.input)
testutils.AssertEqual(t, nextIdx, tc.retIdx, "retIdx mismatch")
testutils.AssertDeepEqual(t, tok, tc.retTok, "retTok mismatch")
assert.Equal(t, nextIdx, tc.retIdx, "retIdx mismatch")
assert.DeepEqual(t, tok, tc.retTok, "retTok mismatch")
})
}
}
@ -225,7 +225,7 @@ func TestTokenize(t *testing.T) {
t.Run(fmt.Sprintf("test case %d", tcIdx), func(t *testing.T) {
tokens := tokenize(tc.input)
testutils.AssertDeepEqual(t, tokens, tc.tokens, "tokens mismatch")
assert.DeepEqual(t, tokens, tc.tokens, "tokens mismatch")
})
}
}

View file

@ -22,12 +22,14 @@ import (
"encoding/base64"
"strconv"
"github.com/dnote/dnote/cli/client"
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/crypt"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/cli/utils"
"github.com/dnote/dnote/pkg/cli/client"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/crypt"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/dnote/dnote/pkg/cli/ui"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -36,7 +38,7 @@ var example = `
dnote login`
// NewCmd returns a new login command
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func NewCmd(ctx context.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
Use: "login",
Short: "Login to dnote server",
@ -48,7 +50,7 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
}
// Do dervies credentials on the client side and requests a session token from the server
func Do(ctx infra.DnoteCtx, email, password string) error {
func Do(ctx context.DnoteCtx, email, password string) error {
presigninResp, err := client.GetPresignin(ctx, email)
if err != nil {
return errors.Wrap(err, "getting presiginin")
@ -78,13 +80,13 @@ func Do(ctx infra.DnoteCtx, email, password string) error {
return errors.Wrap(err, "beginning a transaction")
}
if err := core.UpsertSystem(tx, infra.SystemCipherKey, cipherKeyDecB64); err != nil {
if err := database.UpsertSystem(tx, consts.SystemCipherKey, cipherKeyDecB64); err != nil {
return errors.Wrap(err, "saving enc key")
}
if err := core.UpsertSystem(tx, infra.SystemSessionKey, signinResp.Key); err != nil {
if err := database.UpsertSystem(tx, consts.SystemSessionKey, signinResp.Key); err != nil {
return errors.Wrap(err, "saving session key")
}
if err := core.UpsertSystem(tx, infra.SystemSessionKeyExpiry, strconv.FormatInt(signinResp.ExpiresAt, 10)); err != nil {
if err := database.UpsertSystem(tx, consts.SystemSessionKeyExpiry, strconv.FormatInt(signinResp.ExpiresAt, 10)); err != nil {
return errors.Wrap(err, "saving session key")
}
@ -93,17 +95,17 @@ func Do(ctx infra.DnoteCtx, email, password string) error {
return nil
}
func newRun(ctx infra.DnoteCtx) core.RunEFunc {
func newRun(ctx context.DnoteCtx) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
var email, password string
if err := utils.PromptInput("email", &email); err != nil {
if err := ui.PromptInput("email", &email); err != nil {
return errors.Wrap(err, "getting email input")
}
if email == "" {
return errors.New("Email is empty")
}
if err := utils.PromptPassword("password", &password); err != nil {
if err := ui.PromptPassword("password", &password); err != nil {
return errors.Wrap(err, "getting password input")
}
if password == "" {

View file

@ -21,10 +21,12 @@ package logout
import (
"database/sql"
"github.com/dnote/dnote/cli/client"
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/pkg/cli/client"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -36,7 +38,7 @@ var example = `
dnote logout`
// NewCmd returns a new logout command
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func NewCmd(ctx context.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
Use: "logout",
Short: "Logout from the server",
@ -48,7 +50,7 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
}
// Do performs logout
func Do(ctx infra.DnoteCtx) error {
func Do(ctx context.DnoteCtx) error {
db := ctx.DB
tx, err := db.Begin()
if err != nil {
@ -56,7 +58,7 @@ func Do(ctx infra.DnoteCtx) error {
}
var key string
err = core.GetSystem(tx, infra.SystemSessionKey, &key)
err = database.GetSystem(tx, consts.SystemSessionKey, &key)
if errors.Cause(err) == sql.ErrNoRows {
return ErrNotLoggedIn
} else if err != nil {
@ -68,13 +70,13 @@ func Do(ctx infra.DnoteCtx) error {
return errors.Wrap(err, "requesting logout")
}
if err := core.DeleteSystem(tx, infra.SystemCipherKey); err != nil {
if err := database.DeleteSystem(tx, consts.SystemCipherKey); err != nil {
return errors.Wrap(err, "deleting enc key")
}
if err := core.DeleteSystem(tx, infra.SystemSessionKey); err != nil {
if err := database.DeleteSystem(tx, consts.SystemSessionKey); err != nil {
return errors.Wrap(err, "deleting session key")
}
if err := core.DeleteSystem(tx, infra.SystemSessionKeyExpiry); err != nil {
if err := database.DeleteSystem(tx, consts.SystemSessionKeyExpiry); err != nil {
return errors.Wrap(err, "deleting session key expiry")
}
@ -83,7 +85,7 @@ func Do(ctx infra.DnoteCtx) error {
return nil
}
func newRun(ctx infra.DnoteCtx) core.RunEFunc {
func newRun(ctx context.DnoteCtx) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
err := Do(ctx)
if err == ErrNotLoggedIn {

View file

@ -23,9 +23,9 @@ import (
"fmt"
"strings"
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -52,7 +52,7 @@ func preRun(cmd *cobra.Command, args []string) error {
}
// NewCmd returns a new ls command
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func NewCmd(ctx context.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
Use: "ls <book name?>",
Aliases: []string{"l", "notes"},
@ -67,7 +67,7 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
}
// NewRun returns a new run function for ls
func NewRun(ctx infra.DnoteCtx, nameOnly bool) core.RunEFunc {
func NewRun(ctx context.DnoteCtx, nameOnly bool) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
if err := printBooks(ctx, nameOnly); err != nil {
@ -133,7 +133,7 @@ func printBookLine(info bookInfo, nameOnly bool) {
}
}
func printBooks(ctx infra.DnoteCtx, nameOnly bool) error {
func printBooks(ctx context.DnoteCtx, nameOnly bool) error {
db := ctx.DB
rows, err := db.Query(`SELECT books.label, count(notes.uuid) note_count
@ -165,7 +165,7 @@ func printBooks(ctx infra.DnoteCtx, nameOnly bool) error {
return nil
}
func printNotes(ctx infra.DnoteCtx, bookName string) error {
func printNotes(ctx context.DnoteCtx, bookName string) error {
db := ctx.DB
var bookUUID string

View file

@ -21,10 +21,13 @@ package remove
import (
"fmt"
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/cli/utils"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/dnote/dnote/pkg/cli/output"
"github.com/dnote/dnote/pkg/cli/ui"
"github.com/dnote/dnote/pkg/cli/utils"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -39,7 +42,7 @@ var example = `
dnote delete -b js`
// NewCmd returns a new remove command
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func NewCmd(ctx context.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
Use: "remove",
Short: "Remove a note or a book",
@ -54,7 +57,7 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
return cmd
}
func newRun(ctx infra.DnoteCtx) core.RunEFunc {
func newRun(ctx context.DnoteCtx) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
if targetBookName != "" {
if err := removeBook(ctx, targetBookName); err != nil {
@ -83,17 +86,17 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
}
}
func removeNote(ctx infra.DnoteCtx, noteRowID string) error {
func removeNote(ctx context.DnoteCtx, noteRowID string) error {
db := ctx.DB
noteInfo, err := core.GetNoteInfo(ctx, noteRowID)
noteInfo, err := database.GetNoteInfo(db, noteRowID)
if err != nil {
return err
}
core.PrintNoteInfo(noteInfo)
output.NoteInfo(noteInfo)
ok, err := utils.AskConfirmation("remove this note?", false)
ok, err := ui.Confirm("remove this note?", false)
if err != nil {
return errors.Wrap(err, "getting confirmation")
}
@ -117,15 +120,15 @@ func removeNote(ctx infra.DnoteCtx, noteRowID string) error {
return nil
}
func removeBook(ctx infra.DnoteCtx, bookLabel string) error {
func removeBook(ctx context.DnoteCtx, bookLabel string) error {
db := ctx.DB
bookUUID, err := core.GetBookUUID(ctx, bookLabel)
bookUUID, err := database.GetBookUUID(db, bookLabel)
if err != nil {
return errors.Wrap(err, "finding book uuid")
}
ok, err := utils.AskConfirmation(fmt.Sprintf("delete book '%s' and all its notes?", bookLabel), false)
ok, err := ui.Confirm(fmt.Sprintf("delete book '%s' and all its notes?", bookLabel), false)
if err != nil {
return errors.Wrap(err, "getting confirmation")
}

View file

@ -19,10 +19,11 @@
package root
import (
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/migrate"
"github.com/pkg/errors"
"github.com/dnote/dnote/pkg/cli/context"
// "github.com/dnote/dnote/pkg/cli/core"
// "github.com/dnote/dnote/pkg/cli/infra"
// "github.com/dnote/dnote/pkg/cli/migrate"
// "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -44,24 +45,24 @@ func Execute() error {
}
// Prepare initializes necessary files
func Prepare(ctx infra.DnoteCtx) error {
if err := core.InitFiles(ctx); err != nil {
return errors.Wrap(err, "initializing files")
}
if err := infra.InitDB(ctx); err != nil {
return errors.Wrap(err, "initializing database")
}
if err := core.InitSystem(ctx); err != nil {
return errors.Wrap(err, "initializing system data")
}
if err := migrate.Legacy(ctx); err != nil {
return errors.Wrap(err, "running legacy migration")
}
if err := migrate.Run(ctx, migrate.LocalSequence, migrate.LocalMode); err != nil {
return errors.Wrap(err, "running migration")
}
func Prepare(ctx context.DnoteCtx) error {
// if err := core.InitFiles(ctx); err != nil {
// return errors.Wrap(err, "initializing files")
// }
//
// if err := infra.InitDB(ctx); err != nil {
// return errors.Wrap(err, "initializing database")
// }
// if err := core.InitSystem(ctx); err != nil {
// return errors.Wrap(err, "initializing system data")
// }
//
// if err := migrate.Legacy(ctx); err != nil {
// return errors.Wrap(err, "running legacy migration")
// }
// if err := migrate.Run(ctx, migrate.LocalSequence, migrate.LocalMode); err != nil {
// return errors.Wrap(err, "running migration")
// }
return nil
}

View file

@ -23,11 +23,10 @@ import (
"fmt"
"strings"
"github.com/dnote/dnote/cli/client"
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/utils"
"github.com/dnote/dnote/cli/utils/diff"
"github.com/dnote/dnote/pkg/cli/client"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/utils"
"github.com/dnote/dnote/pkg/cli/utils/diff"
"github.com/pkg/errors"
)
@ -102,7 +101,7 @@ func maxInt64(a, b int64) int64 {
return b
}
func reportBookConflict(tx *infra.DB, body, localBookUUID, serverBookUUID string) (string, error) {
func reportBookConflict(tx *database.DB, body, localBookUUID, serverBookUUID string) (string, error) {
var builder strings.Builder
var localBookName, serverBookName string
@ -123,14 +122,14 @@ func reportBookConflict(tx *infra.DB, body, localBookUUID, serverBookUUID string
return builder.String(), nil
}
func getConflictsBookUUID(tx *infra.DB) (string, error) {
func getConflictsBookUUID(tx *database.DB) (string, error) {
var ret string
err := tx.QueryRow("SELECT uuid FROM books WHERE label = ?", "conflicts").Scan(&ret)
if err == sql.ErrNoRows {
// Create a conflicts book
ret = utils.GenerateUUID()
b := core.NewBook(ret, "conflicts", 0, false, true)
b := database.NewBook(ret, "conflicts", 0, false, true)
err = b.Insert(tx)
if err != nil {
tx.Rollback()
@ -152,7 +151,7 @@ type noteMergeReport struct {
// mergeNoteFields performs a field-by-field merge between the local and the server copy. It returns a merge report
// between the local and the server copy of the note.
func mergeNoteFields(tx *infra.DB, localNote core.Note, serverNote client.SyncFragNote) (*noteMergeReport, error) {
func mergeNoteFields(tx *database.DB, localNote database.Note, serverNote client.SyncFragNote) (*noteMergeReport, error) {
if !localNote.Dirty {
return &noteMergeReport{
body: serverNote.Body,

View file

@ -22,7 +22,7 @@ import (
"fmt"
"testing"
"github.com/dnote/dnote/cli/testutils"
"github.com/dnote/dnote/pkg/assert"
)
func TestReportConflict(t *testing.T) {
@ -128,7 +128,7 @@ fuuz
result := reportBodyConflict(tc.local, tc.server)
t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) {
testutils.AssertDeepEqual(t, result, tc.expected, "result mismatch")
assert.DeepEqual(t, result, tc.expected, "result mismatch")
})
}
}

View file

@ -22,12 +22,15 @@ import (
"database/sql"
"fmt"
"github.com/dnote/dnote/cli/client"
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/crypt"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/cli/migrate"
"github.com/dnote/dnote/pkg/cli/client"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/crypt"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/dnote/dnote/pkg/cli/migrate"
"github.com/dnote/dnote/pkg/cli/upgrade"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -43,7 +46,7 @@ var example = `
var isFullSync bool
// NewCmd returns a new sync command
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func NewCmd(ctx context.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
Use: "sync",
Aliases: []string{"s"},
@ -58,20 +61,20 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
return cmd
}
func getLastSyncAt(tx *infra.DB) (int, error) {
func getLastSyncAt(tx *database.DB) (int, error) {
var ret int
if err := core.GetSystem(tx, infra.SystemLastSyncAt, &ret); err != nil {
if err := database.GetSystem(tx, consts.SystemLastSyncAt, &ret); err != nil {
return ret, errors.Wrap(err, "querying last sync time")
}
return ret, nil
}
func getLastMaxUSN(tx *infra.DB) (int, error) {
func getLastMaxUSN(tx *database.DB) (int, error) {
var ret int
if err := core.GetSystem(tx, infra.SystemLastMaxUSN, &ret); err != nil {
if err := database.GetSystem(tx, consts.SystemLastMaxUSN, &ret); err != nil {
return ret, errors.Wrap(err, "querying last user max_usn")
}
@ -152,7 +155,7 @@ func processFragments(fragments []client.SyncFragment, cipherKey []byte) (syncLi
// getSyncList gets a list of all sync fragments after the specified usn
// and aggregates them into a syncList data structure
func getSyncList(ctx infra.DnoteCtx, afterUSN int) (syncList, error) {
func getSyncList(ctx context.DnoteCtx, afterUSN int) (syncList, error) {
fragments, err := getSyncFragments(ctx, afterUSN)
if err != nil {
return syncList{}, errors.Wrap(err, "getting sync fragments")
@ -168,7 +171,7 @@ func getSyncList(ctx infra.DnoteCtx, afterUSN int) (syncList, error) {
// getSyncFragments repeatedly gets all sync fragments after the specified usn until there is no more new data
// remaining and returns the buffered list
func getSyncFragments(ctx infra.DnoteCtx, afterUSN int) ([]client.SyncFragment, error) {
func getSyncFragments(ctx context.DnoteCtx, afterUSN int) ([]client.SyncFragment, error) {
var buf []client.SyncFragment
nextAfterUSN := afterUSN
@ -197,7 +200,7 @@ func getSyncFragments(ctx infra.DnoteCtx, afterUSN int) ([]client.SyncFragment,
// resolveLabel resolves a book label conflict by repeatedly appending an increasing integer
// to the label until it finds a unique label. It returns the first non-conflicting label.
func resolveLabel(tx *infra.DB, label string) (string, error) {
func resolveLabel(tx *database.DB, label string) (string, error) {
var ret string
for i := 2; ; i++ {
@ -218,7 +221,7 @@ func resolveLabel(tx *infra.DB, label string) (string, error) {
// mergeBook inserts or updates the given book in the local database.
// If a book with a duplicate label exists locally, it renames the duplicate by appending a number.
func mergeBook(tx *infra.DB, b client.SyncFragBook, mode int) error {
func mergeBook(tx *database.DB, b client.SyncFragBook, mode int) error {
var count int
if err := tx.QueryRow("SELECT count(*) FROM books WHERE label = ?", b.Label).Scan(&count); err != nil {
return errors.Wrapf(err, "checking for books with a duplicate label %s", b.Label)
@ -237,7 +240,7 @@ func mergeBook(tx *infra.DB, b client.SyncFragBook, mode int) error {
}
if mode == modeInsert {
book := core.NewBook(b.UUID, b.Label, b.USN, false, false)
book := database.NewBook(b.UUID, b.Label, b.USN, false, false)
if err := book.Insert(tx); err != nil {
return errors.Wrapf(err, "inserting note with uuid %s", b.UUID)
}
@ -252,7 +255,7 @@ func mergeBook(tx *infra.DB, b client.SyncFragBook, mode int) error {
return nil
}
func stepSyncBook(tx *infra.DB, b client.SyncFragBook) error {
func stepSyncBook(tx *database.DB, b client.SyncFragBook) error {
var localUSN int
var dirty bool
err := tx.QueryRow("SELECT usn, dirty FROM books WHERE uuid = ?", b.UUID).Scan(&localUSN, &dirty)
@ -276,7 +279,7 @@ func stepSyncBook(tx *infra.DB, b client.SyncFragBook) error {
return nil
}
func mergeNote(tx *infra.DB, serverNote client.SyncFragNote, localNote core.Note) error {
func mergeNote(tx *database.DB, serverNote client.SyncFragNote, localNote database.Note) error {
var bookDeleted bool
err := tx.QueryRow("SELECT deleted FROM books WHERE uuid = ?", localNote.BookUUID).Scan(&bookDeleted)
if err != nil {
@ -311,8 +314,8 @@ func mergeNote(tx *infra.DB, serverNote client.SyncFragNote, localNote core.Note
return nil
}
func stepSyncNote(tx *infra.DB, n client.SyncFragNote) error {
var localNote core.Note
func stepSyncNote(tx *database.DB, n client.SyncFragNote) error {
var localNote database.Note
err := tx.QueryRow("SELECT body, usn, book_uuid, dirty, deleted FROM notes WHERE uuid = ?", n.UUID).
Scan(&localNote.Body, &localNote.USN, &localNote.BookUUID, &localNote.Dirty, &localNote.Deleted)
if err != nil && err != sql.ErrNoRows {
@ -321,7 +324,7 @@ func stepSyncNote(tx *infra.DB, n client.SyncFragNote) error {
// if note exists in the server and does not exist in the client, insert the note.
if err == sql.ErrNoRows {
note := core.NewNote(n.UUID, n.BookUUID, n.Body, n.AddedOn, n.EditedOn, n.USN, n.Public, n.Deleted, false)
note := database.NewNote(n.UUID, n.BookUUID, n.Body, n.AddedOn, n.EditedOn, n.USN, n.Public, n.Deleted, false)
if err := note.Insert(tx); err != nil {
return errors.Wrapf(err, "inserting note with uuid %s", n.UUID)
@ -335,8 +338,8 @@ func stepSyncNote(tx *infra.DB, n client.SyncFragNote) error {
return nil
}
func fullSyncNote(tx *infra.DB, n client.SyncFragNote) error {
var localNote core.Note
func fullSyncNote(tx *database.DB, n client.SyncFragNote) error {
var localNote database.Note
err := tx.QueryRow("SELECT body, usn, book_uuid, dirty, deleted FROM notes WHERE uuid = ?", n.UUID).
Scan(&localNote.Body, &localNote.USN, &localNote.BookUUID, &localNote.Dirty, &localNote.Deleted)
if err != nil && err != sql.ErrNoRows {
@ -345,7 +348,7 @@ func fullSyncNote(tx *infra.DB, n client.SyncFragNote) error {
// if note exists in the server and does not exist in the client, insert the note.
if err == sql.ErrNoRows {
note := core.NewNote(n.UUID, n.BookUUID, n.Body, n.AddedOn, n.EditedOn, n.USN, n.Public, n.Deleted, false)
note := database.NewNote(n.UUID, n.BookUUID, n.Body, n.AddedOn, n.EditedOn, n.USN, n.Public, n.Deleted, false)
if err := note.Insert(tx); err != nil {
return errors.Wrapf(err, "inserting note with uuid %s", n.UUID)
@ -359,7 +362,7 @@ func fullSyncNote(tx *infra.DB, n client.SyncFragNote) error {
return nil
}
func syncDeleteNote(tx *infra.DB, noteUUID string) error {
func syncDeleteNote(tx *database.DB, noteUUID string) error {
var localUSN int
var dirty bool
err := tx.QueryRow("SELECT usn, dirty FROM notes WHERE uuid = ?", noteUUID).Scan(&localUSN, &dirty)
@ -384,7 +387,7 @@ func syncDeleteNote(tx *infra.DB, noteUUID string) error {
}
// checkNotesPristine checks that none of the notes in the given book are dirty
func checkNotesPristine(tx *infra.DB, bookUUID string) (bool, error) {
func checkNotesPristine(tx *database.DB, bookUUID string) (bool, error) {
var count int
if err := tx.QueryRow("SELECT count(*) FROM notes WHERE book_uuid = ? AND dirty = ?", bookUUID, true).Scan(&count); err != nil {
return false, errors.Wrapf(err, "counting notes that are dirty in book %s", bookUUID)
@ -397,7 +400,7 @@ func checkNotesPristine(tx *infra.DB, bookUUID string) (bool, error) {
return true, nil
}
func syncDeleteBook(tx *infra.DB, bookUUID string) error {
func syncDeleteBook(tx *database.DB, bookUUID string) error {
var localUSN int
var dirty bool
err := tx.QueryRow("SELECT usn, dirty FROM books WHERE uuid = ?", bookUUID).Scan(&localUSN, &dirty)
@ -443,7 +446,7 @@ func syncDeleteBook(tx *infra.DB, bookUUID string) error {
return nil
}
func fullSyncBook(tx *infra.DB, b client.SyncFragBook) error {
func fullSyncBook(tx *database.DB, b client.SyncFragBook) error {
var localUSN int
var dirty bool
err := tx.QueryRow("SELECT usn, dirty FROM books WHERE uuid = ?", b.UUID).Scan(&localUSN, &dirty)
@ -495,7 +498,7 @@ func checkBookInList(uuid string, list *syncList) bool {
// judging by the full list of resources in the server. Concretely, the only acceptable
// situation in which a local note is not present in the server is if it is new and has not been
// uploaded (i.e. dirty and usn is 0). Otherwise, it is a result of some kind of error and should be cleaned.
func cleanLocalNotes(tx *infra.DB, fullList *syncList) error {
func cleanLocalNotes(tx *database.DB, fullList *syncList) error {
rows, err := tx.Query("SELECT uuid, usn, dirty FROM notes")
if err != nil {
return errors.Wrap(err, "getting local notes")
@ -503,7 +506,7 @@ func cleanLocalNotes(tx *infra.DB, fullList *syncList) error {
defer rows.Close()
for rows.Next() {
var note core.Note
var note database.Note
if err := rows.Scan(&note.UUID, &note.USN, &note.Dirty); err != nil {
return errors.Wrap(err, "scanning a row for local note")
}
@ -521,7 +524,7 @@ func cleanLocalNotes(tx *infra.DB, fullList *syncList) error {
}
// cleanLocalBooks deletes from the local database any books that are in invalid state
func cleanLocalBooks(tx *infra.DB, fullList *syncList) error {
func cleanLocalBooks(tx *database.DB, fullList *syncList) error {
rows, err := tx.Query("SELECT uuid, usn, dirty FROM books")
if err != nil {
return errors.Wrap(err, "getting local books")
@ -529,7 +532,7 @@ func cleanLocalBooks(tx *infra.DB, fullList *syncList) error {
defer rows.Close()
for rows.Next() {
var book core.Book
var book database.Book
if err := rows.Scan(&book.UUID, &book.USN, &book.Dirty); err != nil {
return errors.Wrap(err, "scanning a row for local book")
}
@ -546,7 +549,7 @@ func cleanLocalBooks(tx *infra.DB, fullList *syncList) error {
return nil
}
func fullSync(ctx infra.DnoteCtx, tx *infra.DB) error {
func fullSync(ctx context.DnoteCtx, tx *database.DB) error {
log.Debug("performing a full sync\n")
log.Info("resolving delta.")
@ -597,7 +600,7 @@ func fullSync(ctx infra.DnoteCtx, tx *infra.DB) error {
return nil
}
func stepSync(ctx infra.DnoteCtx, tx *infra.DB, afterUSN int) error {
func stepSync(ctx context.DnoteCtx, tx *database.DB, afterUSN int) error {
log.Debug("performing a step sync\n")
log.Info("resolving delta.")
@ -641,7 +644,7 @@ func stepSync(ctx infra.DnoteCtx, tx *infra.DB, afterUSN int) error {
return nil
}
func sendBooks(ctx infra.DnoteCtx, tx *infra.DB) (bool, error) {
func sendBooks(ctx context.DnoteCtx, tx *database.DB) (bool, error) {
isBehind := false
rows, err := tx.Query("SELECT uuid, label, usn, deleted FROM books WHERE dirty")
@ -651,7 +654,7 @@ func sendBooks(ctx infra.DnoteCtx, tx *infra.DB) (bool, error) {
defer rows.Close()
for rows.Next() {
var book core.Book
var book database.Book
if err = rows.Scan(&book.UUID, &book.Label, &book.USN, &book.Deleted); err != nil {
return isBehind, errors.Wrap(err, "scanning a syncable book")
@ -745,7 +748,7 @@ func sendBooks(ctx infra.DnoteCtx, tx *infra.DB) (bool, error) {
return isBehind, nil
}
func sendNotes(ctx infra.DnoteCtx, tx *infra.DB) (bool, error) {
func sendNotes(ctx context.DnoteCtx, tx *database.DB) (bool, error) {
isBehind := false
rows, err := tx.Query("SELECT uuid, book_uuid, body, public, deleted, usn, added_on FROM notes WHERE dirty")
@ -755,7 +758,7 @@ func sendNotes(ctx infra.DnoteCtx, tx *infra.DB) (bool, error) {
defer rows.Close()
for rows.Next() {
var note core.Note
var note database.Note
if err = rows.Scan(&note.UUID, &note.BookUUID, &note.Body, &note.Public, &note.Deleted, &note.USN, &note.AddedOn); err != nil {
return isBehind, errors.Wrap(err, "scanning a syncable note")
@ -845,7 +848,7 @@ func sendNotes(ctx infra.DnoteCtx, tx *infra.DB) (bool, error) {
return isBehind, nil
}
func sendChanges(ctx infra.DnoteCtx, tx *infra.DB) (bool, error) {
func sendChanges(ctx context.DnoteCtx, tx *database.DB) (bool, error) {
log.Info("sending changes.")
var delta int
@ -870,23 +873,23 @@ func sendChanges(ctx infra.DnoteCtx, tx *infra.DB) (bool, error) {
return isBehind, nil
}
func updateLastMaxUSN(tx *infra.DB, val int) error {
if err := core.UpdateSystem(tx, infra.SystemLastMaxUSN, val); err != nil {
return errors.Wrapf(err, "updating %s", infra.SystemLastMaxUSN)
func updateLastMaxUSN(tx *database.DB, val int) error {
if err := database.UpdateSystem(tx, consts.SystemLastMaxUSN, val); err != nil {
return errors.Wrapf(err, "updating %s", consts.SystemLastMaxUSN)
}
return nil
}
func updateLastSyncAt(tx *infra.DB, val int64) error {
if err := core.UpdateSystem(tx, infra.SystemLastSyncAt, val); err != nil {
return errors.Wrapf(err, "updating %s", infra.SystemLastSyncAt)
func updateLastSyncAt(tx *database.DB, val int64) error {
if err := database.UpdateSystem(tx, consts.SystemLastSyncAt, val); err != nil {
return errors.Wrapf(err, "updating %s", consts.SystemLastSyncAt)
}
return nil
}
func saveSyncState(tx *infra.DB, serverTime int64, serverMaxUSN int) error {
func saveSyncState(tx *database.DB, serverTime int64, serverMaxUSN int) error {
if err := updateLastMaxUSN(tx, serverMaxUSN); err != nil {
return errors.Wrap(err, "updating last max usn")
}
@ -897,7 +900,7 @@ func saveSyncState(tx *infra.DB, serverTime int64, serverMaxUSN int) error {
return nil
}
func newRun(ctx infra.DnoteCtx) core.RunEFunc {
func newRun(ctx context.DnoteCtx) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
if ctx.SessionKey == "" || ctx.CipherKey == nil {
return errors.New("not logged in")
@ -971,7 +974,7 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc {
log.Success("success\n")
if err := core.CheckUpdate(ctx); err != nil {
if err := upgrade.Check(ctx); err != nil {
log.Error(errors.Wrap(err, "automatically checking updates").Error())
}

File diff suppressed because it is too large Load diff

View file

@ -21,12 +21,12 @@ package version
import (
"fmt"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/spf13/cobra"
)
// NewCmd returns a new version command
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func NewCmd(ctx context.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
Use: "version",
Short: "Print the version number of Dnote",

View file

@ -19,14 +19,14 @@
package view
import (
"github.com/dnote/dnote/cli/core"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/dnote/dnote/cli/cmd/cat"
"github.com/dnote/dnote/cli/cmd/ls"
"github.com/dnote/dnote/cli/utils"
"github.com/dnote/dnote/pkg/cli/cmd/cat"
"github.com/dnote/dnote/pkg/cli/cmd/ls"
"github.com/dnote/dnote/pkg/cli/utils"
)
var example = `
@ -51,7 +51,7 @@ func preRun(cmd *cobra.Command, args []string) error {
}
// NewCmd returns a new view command
func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
func NewCmd(ctx context.DnoteCtx) *cobra.Command {
cmd := &cobra.Command{
Use: "view <book name?> <note index?>",
Aliases: []string{"v"},
@ -67,9 +67,9 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command {
return cmd
}
func newRun(ctx infra.DnoteCtx) core.RunEFunc {
func newRun(ctx context.DnoteCtx) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
var run core.RunEFunc
var run infra.RunEFunc
if len(args) == 0 {
run = ls.NewRun(ctx, nameOnly)

46
pkg/cli/consts/consts.go Normal file
View file

@ -0,0 +1,46 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
// Package consts provides definitions of constants
package consts
var (
// DnoteDirName is the name of the directory containing dnote files
DnoteDirName = ".dnote"
// DnoteDBFileName is a filename for the Dnote SQLite database
DnoteDBFileName = "dnote.db"
// TmpContentFilename is the filename for a temporary content
TmpContentFilename = "DNOTE_TMPCONTENT.md"
// SystemSchema is the key for schema in the system table
SystemSchema = "schema"
// SystemRemoteSchema is the key for remote schema in the system table
SystemRemoteSchema = "remote_schema"
// SystemLastSyncAt is the timestamp of the server at the last sync
SystemLastSyncAt = "last_sync_time"
// SystemLastMaxUSN is the user's max_usn from the server at the alst sync
SystemLastMaxUSN = "last_max_usn"
// SystemLastUpgrade is the timestamp at which the system more recently checked for an upgrade
SystemLastUpgrade = "last_upgrade"
// SystemCipherKey is the encryption key
SystemCipherKey = "enc_key"
// SystemSessionKey is the session key
SystemSessionKey = "session_token"
// SystemSessionKeyExpiry is the timestamp at which the session key will expire
SystemSessionKeyExpiry = "session_token_expiry"
)

36
pkg/cli/context/ctx.go Normal file
View file

@ -0,0 +1,36 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
// Package context defines dnote context
package context
import (
"github.com/dnote/dnote/pkg/cli/database"
)
// DnoteCtx is a context holding the information of the current runtime
type DnoteCtx struct {
HomeDir string
DnoteDir string
APIEndpoint string
Version string
DB *database.DB
SessionKey string
SessionKeyExpiry int64
CipherKey []byte
}

View file

@ -0,0 +1,48 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package context
import (
"encoding/base64"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/pkg/errors"
)
// GetCipherKey retrieves the cipher key and decode the base64 into bytes.
func (ctx DnoteCtx) GetCipherKey() ([]byte, error) {
tx, err := ctx.DB.Begin()
if err != nil {
return nil, errors.Wrap(err, "beginning transaction")
}
var cipherKeyB64 string
err = database.GetSystem(tx, consts.SystemCipherKey, &cipherKeyB64)
if err != nil {
return []byte{}, errors.Wrap(err, "getting enc key")
}
cipherKey, err := base64.StdEncoding.DecodeString(cipherKeyB64)
if err != nil {
return nil, errors.Wrap(err, "decoding cipherKey from base64")
}
return cipherKey, nil
}

View file

@ -0,0 +1,41 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package context
import (
"fmt"
"testing"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/database"
)
// InitTestCtx initializes a test context
func InitTestCtx(t *testing.T, dnoteDir string, dbOpts *database.TestDBOptions) DnoteCtx {
dbPath := fmt.Sprintf("%s/%s", dnoteDir, consts.DnoteDBFileName)
db := database.InitTestDB(t, dbPath, dbOpts)
return DnoteCtx{DB: db, DnoteDir: dnoteDir}
}
// TeardownTestCtx cleans up the test context
func TeardownTestCtx(t *testing.T, ctx DnoteCtx) {
database.CloseTestDB(t, ctx.DB)
}

View file

@ -27,7 +27,6 @@ import (
"encoding/base64"
"io"
"github.com/dnote/dnote/cli/log"
"github.com/pkg/errors"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/pbkdf2"
@ -51,7 +50,6 @@ func runHkdf(secret, salt, info []byte) ([]byte, error) {
// and an authentication key
func MakeKeys(password, email []byte, iteration int) ([]byte, []byte, error) {
masterKey := pbkdf2.Key([]byte(password), []byte(email), iteration, 32, sha256.New)
log.Debug("email: %s, password: %s", email, password)
authKey, err := runHkdf(masterKey, email, []byte("auth"))
if err != nil {

View file

@ -25,7 +25,7 @@ import (
"fmt"
"testing"
"github.com/dnote/dnote/cli/testutils"
"github.com/dnote/dnote/pkg/assert"
"github.com/pkg/errors"
)
@ -77,7 +77,7 @@ func TestAesGcmEncrypt(t *testing.T) {
t.Fatal(errors.Wrap(err, "decode"))
}
testutils.AssertDeepEqual(t, plaintext, tc.plaintext, "plaintext mismatch")
assert.DeepEqual(t, plaintext, tc.plaintext, "plaintext mismatch")
})
}
}
@ -112,7 +112,7 @@ func TestAesGcmDecrypt(t *testing.T) {
t.Fatal(errors.Wrap(err, "performing decryption"))
}
testutils.AssertDeepEqual(t, plaintext, []byte(tc.expectedPlaintext), "plaintext mismatch")
assert.DeepEqual(t, plaintext, []byte(tc.expectedPlaintext), "plaintext mismatch")
})
}
}

View file

@ -16,10 +16,9 @@
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package core
package database
import (
"github.com/dnote/dnote/cli/infra"
"github.com/pkg/errors"
)
@ -62,7 +61,7 @@ func NewNote(uuid, bookUUID, body string, addedOn, editedOn int64, usn int, publ
}
// Insert inserts a new note
func (n Note) Insert(db *infra.DB) error {
func (n Note) Insert(db *DB) error {
_, err := db.Exec("INSERT INTO notes (uuid, book_uuid, body, added_on, edited_on, usn, public, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
n.UUID, n.BookUUID, n.Body, n.AddedOn, n.EditedOn, n.USN, n.Public, n.Deleted, n.Dirty)
@ -74,7 +73,7 @@ func (n Note) Insert(db *infra.DB) error {
}
// Update updates the note with the given data
func (n Note) Update(db *infra.DB) error {
func (n Note) Update(db *DB) error {
_, err := db.Exec("UPDATE notes SET book_uuid = ?, body = ?, added_on = ?, edited_on = ?, usn = ?, public = ?, deleted = ?, dirty = ? WHERE uuid = ?",
n.BookUUID, n.Body, n.AddedOn, n.EditedOn, n.USN, n.Public, n.Deleted, n.Dirty, n.UUID)
@ -86,7 +85,7 @@ func (n Note) Update(db *infra.DB) error {
}
// UpdateUUID updates the uuid of a book
func (n *Note) UpdateUUID(db *infra.DB, newUUID string) error {
func (n *Note) UpdateUUID(db *DB, newUUID string) error {
_, err := db.Exec("UPDATE notes SET uuid = ? WHERE uuid = ?", newUUID, n.UUID)
if err != nil {
@ -99,7 +98,7 @@ func (n *Note) UpdateUUID(db *infra.DB, newUUID string) error {
}
// Expunge hard-deletes the note from the database
func (n Note) Expunge(db *infra.DB) error {
func (n Note) Expunge(db *DB) error {
_, err := db.Exec("DELETE FROM notes WHERE uuid = ?", n.UUID)
if err != nil {
return errors.Wrap(err, "expunging a note locally")
@ -120,7 +119,7 @@ func NewBook(uuid, label string, usn int, deleted, dirty bool) Book {
}
// Insert inserts a new book
func (b Book) Insert(db *infra.DB) error {
func (b Book) Insert(db *DB) error {
_, err := db.Exec("INSERT INTO books (uuid, label, usn, dirty, deleted) VALUES (?, ?, ?, ?, ?)",
b.UUID, b.Label, b.USN, b.Dirty, b.Deleted)
@ -132,7 +131,7 @@ func (b Book) Insert(db *infra.DB) error {
}
// Update updates the book with the given data
func (b Book) Update(db *infra.DB) error {
func (b Book) Update(db *DB) error {
_, err := db.Exec("UPDATE books SET label = ?, usn = ?, dirty = ?, deleted = ? WHERE uuid = ?",
b.Label, b.USN, b.Dirty, b.Deleted, b.UUID)
@ -144,7 +143,7 @@ func (b Book) Update(db *infra.DB) error {
}
// UpdateUUID updates the uuid of a book
func (b *Book) UpdateUUID(db *infra.DB, newUUID string) error {
func (b *Book) UpdateUUID(db *DB, newUUID string) error {
_, err := db.Exec("UPDATE books SET uuid = ? WHERE uuid = ?", newUUID, b.UUID)
if err != nil {
@ -157,7 +156,7 @@ func (b *Book) UpdateUUID(db *infra.DB, newUUID string) error {
}
// Expunge hard-deletes the book from the database
func (b Book) Expunge(db *infra.DB) error {
func (b Book) Expunge(db *DB) error {
_, err := db.Exec("DELETE FROM books WHERE uuid = ?", b.UUID)
if err != nil {
return errors.Wrap(err, "expunging a book locally")

View file

@ -16,13 +16,13 @@
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package core
package database
import (
"fmt"
"testing"
"github.com/dnote/dnote/cli/testutils"
"github.com/dnote/dnote/pkg/assert"
"github.com/pkg/errors"
)
@ -65,15 +65,15 @@ func TestNewNote(t *testing.T) {
for idx, tc := range testCases {
got := NewNote(tc.uuid, tc.bookUUID, tc.body, tc.addedOn, tc.editedOn, tc.usn, tc.public, tc.deleted, tc.dirty)
testutils.AssertEqual(t, got.UUID, tc.uuid, fmt.Sprintf("UUID mismatch for test case %d", idx))
testutils.AssertEqual(t, got.BookUUID, tc.bookUUID, fmt.Sprintf("BookUUID mismatch for test case %d", idx))
testutils.AssertEqual(t, got.Body, tc.body, fmt.Sprintf("Body mismatch for test case %d", idx))
testutils.AssertEqual(t, got.AddedOn, tc.addedOn, fmt.Sprintf("AddedOn mismatch for test case %d", idx))
testutils.AssertEqual(t, got.EditedOn, tc.editedOn, fmt.Sprintf("EditedOn mismatch for test case %d", idx))
testutils.AssertEqual(t, got.USN, tc.usn, fmt.Sprintf("USN mismatch for test case %d", idx))
testutils.AssertEqual(t, got.Public, tc.public, fmt.Sprintf("Public mismatch for test case %d", idx))
testutils.AssertEqual(t, got.Deleted, tc.deleted, fmt.Sprintf("Deleted mismatch for test case %d", idx))
testutils.AssertEqual(t, got.Dirty, tc.dirty, fmt.Sprintf("Dirty mismatch for test case %d", idx))
assert.Equal(t, got.UUID, tc.uuid, fmt.Sprintf("UUID mismatch for test case %d", idx))
assert.Equal(t, got.BookUUID, tc.bookUUID, fmt.Sprintf("BookUUID mismatch for test case %d", idx))
assert.Equal(t, got.Body, tc.body, fmt.Sprintf("Body mismatch for test case %d", idx))
assert.Equal(t, got.AddedOn, tc.addedOn, fmt.Sprintf("AddedOn mismatch for test case %d", idx))
assert.Equal(t, got.EditedOn, tc.editedOn, fmt.Sprintf("EditedOn mismatch for test case %d", idx))
assert.Equal(t, got.USN, tc.usn, fmt.Sprintf("USN mismatch for test case %d", idx))
assert.Equal(t, got.Public, tc.public, fmt.Sprintf("Public mismatch for test case %d", idx))
assert.Equal(t, got.Deleted, tc.deleted, fmt.Sprintf("Deleted mismatch for test case %d", idx))
assert.Equal(t, got.Dirty, tc.dirty, fmt.Sprintf("Dirty mismatch for test case %d", idx))
}
}
@ -116,8 +116,8 @@ func TestNoteInsert(t *testing.T) {
for idx, tc := range testCases {
func() {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
n := Note{
UUID: tc.uuid,
@ -132,8 +132,6 @@ func TestNoteInsert(t *testing.T) {
}
// execute
db := ctx.DB
tx, err := db.Begin()
if err != nil {
t.Fatalf(errors.Wrap(err, fmt.Sprintf("beginning a transaction for test case %d", idx)).Error())
@ -151,19 +149,19 @@ func TestNoteInsert(t *testing.T) {
var addedOn, editedOn int64
var usn int
var public, deleted, dirty bool
testutils.MustScan(t, "getting n1",
MustScan(t, "getting n1",
db.QueryRow("SELECT uuid, book_uuid, body, added_on, edited_on, usn, public, deleted, dirty FROM notes WHERE uuid = ?", tc.uuid),
&uuid, &bookUUID, &body, &addedOn, &editedOn, &usn, &public, &deleted, &dirty)
testutils.AssertEqual(t, uuid, tc.uuid, fmt.Sprintf("uuid mismatch for test case %d", idx))
testutils.AssertEqual(t, bookUUID, tc.bookUUID, fmt.Sprintf("bookUUID mismatch for test case %d", idx))
testutils.AssertEqual(t, body, tc.body, fmt.Sprintf("body mismatch for test case %d", idx))
testutils.AssertEqual(t, addedOn, tc.addedOn, fmt.Sprintf("addedOn mismatch for test case %d", idx))
testutils.AssertEqual(t, editedOn, tc.editedOn, fmt.Sprintf("editedOn mismatch for test case %d", idx))
testutils.AssertEqual(t, usn, tc.usn, fmt.Sprintf("usn mismatch for test case %d", idx))
testutils.AssertEqual(t, public, tc.public, fmt.Sprintf("public mismatch for test case %d", idx))
testutils.AssertEqual(t, deleted, tc.deleted, fmt.Sprintf("deleted mismatch for test case %d", idx))
testutils.AssertEqual(t, dirty, tc.dirty, fmt.Sprintf("dirty mismatch for test case %d", idx))
assert.Equal(t, uuid, tc.uuid, fmt.Sprintf("uuid mismatch for test case %d", idx))
assert.Equal(t, bookUUID, tc.bookUUID, fmt.Sprintf("bookUUID mismatch for test case %d", idx))
assert.Equal(t, body, tc.body, fmt.Sprintf("body mismatch for test case %d", idx))
assert.Equal(t, addedOn, tc.addedOn, fmt.Sprintf("addedOn mismatch for test case %d", idx))
assert.Equal(t, editedOn, tc.editedOn, fmt.Sprintf("editedOn mismatch for test case %d", idx))
assert.Equal(t, usn, tc.usn, fmt.Sprintf("usn mismatch for test case %d", idx))
assert.Equal(t, public, tc.public, fmt.Sprintf("public mismatch for test case %d", idx))
assert.Equal(t, deleted, tc.deleted, fmt.Sprintf("deleted mismatch for test case %d", idx))
assert.Equal(t, dirty, tc.dirty, fmt.Sprintf("dirty mismatch for test case %d", idx))
}()
}
}
@ -264,8 +262,8 @@ func TestNoteUpdate(t *testing.T) {
for idx, tc := range testCases {
func() {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
n1 := Note{
UUID: tc.uuid,
@ -290,9 +288,8 @@ func TestNoteUpdate(t *testing.T) {
Dirty: false,
}
db := ctx.DB
testutils.MustExec(t, fmt.Sprintf("inserting n1 for test case %d", idx), db, "INSERT INTO notes (uuid, book_uuid, usn, added_on, edited_on, body, public, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", n1.UUID, n1.BookUUID, n1.USN, n1.AddedOn, n1.EditedOn, n1.Body, n1.Public, n1.Deleted, n1.Dirty)
testutils.MustExec(t, fmt.Sprintf("inserting n2 for test case %d", idx), db, "INSERT INTO notes (uuid, book_uuid, usn, added_on, edited_on, body, public, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", n2.UUID, n2.BookUUID, n2.USN, n2.AddedOn, n2.EditedOn, n2.Body, n2.Public, n2.Deleted, n2.Dirty)
MustExec(t, fmt.Sprintf("inserting n1 for test case %d", idx), db, "INSERT INTO notes (uuid, book_uuid, usn, added_on, edited_on, body, public, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", n1.UUID, n1.BookUUID, n1.USN, n1.AddedOn, n1.EditedOn, n1.Body, n1.Public, n1.Deleted, n1.Dirty)
MustExec(t, fmt.Sprintf("inserting n2 for test case %d", idx), db, "INSERT INTO notes (uuid, book_uuid, usn, added_on, edited_on, body, public, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", n2.UUID, n2.BookUUID, n2.USN, n2.AddedOn, n2.EditedOn, n2.Body, n2.Public, n2.Deleted, n2.Dirty)
// execute
tx, err := db.Begin()
@ -317,32 +314,32 @@ func TestNoteUpdate(t *testing.T) {
// test
var n1Record, n2Record Note
testutils.MustScan(t, "getting n1",
MustScan(t, "getting n1",
db.QueryRow("SELECT uuid, book_uuid, body, added_on, edited_on, usn, public, deleted, dirty FROM notes WHERE uuid = ?", tc.uuid),
&n1Record.UUID, &n1Record.BookUUID, &n1Record.Body, &n1Record.AddedOn, &n1Record.EditedOn, &n1Record.USN, &n1Record.Public, &n1Record.Deleted, &n1Record.Dirty)
testutils.MustScan(t, "getting n2",
MustScan(t, "getting n2",
db.QueryRow("SELECT uuid, book_uuid, body, added_on, edited_on, usn, public, deleted, dirty FROM notes WHERE uuid = ?", n2.UUID),
&n2Record.UUID, &n2Record.BookUUID, &n2Record.Body, &n2Record.AddedOn, &n2Record.EditedOn, &n2Record.USN, &n2Record.Public, &n2Record.Deleted, &n2Record.Dirty)
testutils.AssertEqual(t, n1Record.UUID, n1.UUID, fmt.Sprintf("n1 uuid mismatch for test case %d", idx))
testutils.AssertEqual(t, n1Record.BookUUID, tc.newBookUUID, fmt.Sprintf("n1 bookUUID mismatch for test case %d", idx))
testutils.AssertEqual(t, n1Record.Body, tc.newBody, fmt.Sprintf("n1 body mismatch for test case %d", idx))
testutils.AssertEqual(t, n1Record.AddedOn, n1.AddedOn, fmt.Sprintf("n1 addedOn mismatch for test case %d", idx))
testutils.AssertEqual(t, n1Record.EditedOn, tc.newEditedOn, fmt.Sprintf("n1 editedOn mismatch for test case %d", idx))
testutils.AssertEqual(t, n1Record.USN, tc.newUSN, fmt.Sprintf("n1 usn mismatch for test case %d", idx))
testutils.AssertEqual(t, n1Record.Public, tc.newPublic, fmt.Sprintf("n1 public mismatch for test case %d", idx))
testutils.AssertEqual(t, n1Record.Deleted, tc.newDeleted, fmt.Sprintf("n1 deleted mismatch for test case %d", idx))
testutils.AssertEqual(t, n1Record.Dirty, tc.newDirty, fmt.Sprintf("n1 dirty mismatch for test case %d", idx))
assert.Equal(t, n1Record.UUID, n1.UUID, fmt.Sprintf("n1 uuid mismatch for test case %d", idx))
assert.Equal(t, n1Record.BookUUID, tc.newBookUUID, fmt.Sprintf("n1 bookUUID mismatch for test case %d", idx))
assert.Equal(t, n1Record.Body, tc.newBody, fmt.Sprintf("n1 body mismatch for test case %d", idx))
assert.Equal(t, n1Record.AddedOn, n1.AddedOn, fmt.Sprintf("n1 addedOn mismatch for test case %d", idx))
assert.Equal(t, n1Record.EditedOn, tc.newEditedOn, fmt.Sprintf("n1 editedOn mismatch for test case %d", idx))
assert.Equal(t, n1Record.USN, tc.newUSN, fmt.Sprintf("n1 usn mismatch for test case %d", idx))
assert.Equal(t, n1Record.Public, tc.newPublic, fmt.Sprintf("n1 public mismatch for test case %d", idx))
assert.Equal(t, n1Record.Deleted, tc.newDeleted, fmt.Sprintf("n1 deleted mismatch for test case %d", idx))
assert.Equal(t, n1Record.Dirty, tc.newDirty, fmt.Sprintf("n1 dirty mismatch for test case %d", idx))
testutils.AssertEqual(t, n2Record.UUID, n2.UUID, fmt.Sprintf("n2 uuid mismatch for test case %d", idx))
testutils.AssertEqual(t, n2Record.BookUUID, n2.BookUUID, fmt.Sprintf("n2 bookUUID mismatch for test case %d", idx))
testutils.AssertEqual(t, n2Record.Body, n2.Body, fmt.Sprintf("n2 body mismatch for test case %d", idx))
testutils.AssertEqual(t, n2Record.AddedOn, n2.AddedOn, fmt.Sprintf("n2 addedOn mismatch for test case %d", idx))
testutils.AssertEqual(t, n2Record.EditedOn, n2.EditedOn, fmt.Sprintf("n2 editedOn mismatch for test case %d", idx))
testutils.AssertEqual(t, n2Record.USN, n2.USN, fmt.Sprintf("n2 usn mismatch for test case %d", idx))
testutils.AssertEqual(t, n2Record.Public, n2.Public, fmt.Sprintf("n2 public mismatch for test case %d", idx))
testutils.AssertEqual(t, n2Record.Deleted, n2.Deleted, fmt.Sprintf("n2 deleted mismatch for test case %d", idx))
testutils.AssertEqual(t, n2Record.Dirty, n2.Dirty, fmt.Sprintf("n2 dirty mismatch for test case %d", idx))
assert.Equal(t, n2Record.UUID, n2.UUID, fmt.Sprintf("n2 uuid mismatch for test case %d", idx))
assert.Equal(t, n2Record.BookUUID, n2.BookUUID, fmt.Sprintf("n2 bookUUID mismatch for test case %d", idx))
assert.Equal(t, n2Record.Body, n2.Body, fmt.Sprintf("n2 body mismatch for test case %d", idx))
assert.Equal(t, n2Record.AddedOn, n2.AddedOn, fmt.Sprintf("n2 addedOn mismatch for test case %d", idx))
assert.Equal(t, n2Record.EditedOn, n2.EditedOn, fmt.Sprintf("n2 editedOn mismatch for test case %d", idx))
assert.Equal(t, n2Record.USN, n2.USN, fmt.Sprintf("n2 usn mismatch for test case %d", idx))
assert.Equal(t, n2Record.Public, n2.Public, fmt.Sprintf("n2 public mismatch for test case %d", idx))
assert.Equal(t, n2Record.Deleted, n2.Deleted, fmt.Sprintf("n2 deleted mismatch for test case %d", idx))
assert.Equal(t, n2Record.Dirty, n2.Dirty, fmt.Sprintf("n2 dirty mismatch for test case %d", idx))
}()
}
}
@ -362,8 +359,8 @@ func TestNoteUpdateUUID(t *testing.T) {
for idx, tc := range testCases {
t.Run(fmt.Sprintf("testCase%d", idx), func(t *testing.T) {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
n1 := Note{
UUID: "n1-uuid",
@ -384,9 +381,8 @@ func TestNoteUpdateUUID(t *testing.T) {
Dirty: false,
}
db := ctx.DB
testutils.MustExec(t, "inserting n1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?)", n1.UUID, n1.BookUUID, n1.Body, n1.AddedOn, n1.USN, n1.Deleted, n1.Dirty)
testutils.MustExec(t, "inserting n2", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?)", n2.UUID, n2.BookUUID, n2.Body, n2.AddedOn, n2.USN, n2.Deleted, n2.Dirty)
MustExec(t, "inserting n1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?)", n1.UUID, n1.BookUUID, n1.Body, n1.AddedOn, n1.USN, n1.Deleted, n1.Dirty)
MustExec(t, "inserting n2", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?)", n2.UUID, n2.BookUUID, n2.Body, n2.AddedOn, n2.USN, n2.Deleted, n2.Dirty)
// execute
tx, err := db.Begin()
@ -402,24 +398,24 @@ func TestNoteUpdateUUID(t *testing.T) {
// test
var n1Record, n2Record Note
testutils.MustScan(t, "getting n1",
MustScan(t, "getting n1",
db.QueryRow("SELECT uuid, body, usn, deleted, dirty FROM notes WHERE body = ?", "n1-body"),
&n1Record.UUID, &n1Record.Body, &n1Record.USN, &n1Record.Deleted, &n1Record.Dirty)
testutils.MustScan(t, "getting n2",
MustScan(t, "getting n2",
db.QueryRow("SELECT uuid, body, usn, deleted, dirty FROM notes WHERE body = ?", "n2-body"),
&n2Record.UUID, &n2Record.Body, &n2Record.USN, &n2Record.Deleted, &n2Record.Dirty)
testutils.AssertEqual(t, n1.UUID, tc.newUUID, "n1 original reference uuid mismatch")
testutils.AssertEqual(t, n1Record.UUID, tc.newUUID, "n1 uuid mismatch")
testutils.AssertEqual(t, n2Record.UUID, n2.UUID, "n2 uuid mismatch")
assert.Equal(t, n1.UUID, tc.newUUID, "n1 original reference uuid mismatch")
assert.Equal(t, n1Record.UUID, tc.newUUID, "n1 uuid mismatch")
assert.Equal(t, n2Record.UUID, n2.UUID, "n2 uuid mismatch")
})
}
}
func TestNoteExpunge(t *testing.T) {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
n1 := Note{
UUID: "n1-uuid",
@ -444,9 +440,8 @@ func TestNoteExpunge(t *testing.T) {
Dirty: false,
}
db := ctx.DB
testutils.MustExec(t, "inserting n1", db, "INSERT INTO notes (uuid, book_uuid, usn, added_on, edited_on, body, public, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", n1.UUID, n1.BookUUID, n1.USN, n1.AddedOn, n1.EditedOn, n1.Body, n1.Public, n1.Deleted, n1.Dirty)
testutils.MustExec(t, "inserting n2", db, "INSERT INTO notes (uuid, book_uuid, usn, added_on, edited_on, body, public, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", n2.UUID, n2.BookUUID, n2.USN, n2.AddedOn, n2.EditedOn, n2.Body, n2.Public, n2.Deleted, n2.Dirty)
MustExec(t, "inserting n1", db, "INSERT INTO notes (uuid, book_uuid, usn, added_on, edited_on, body, public, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", n1.UUID, n1.BookUUID, n1.USN, n1.AddedOn, n1.EditedOn, n1.Body, n1.Public, n1.Deleted, n1.Dirty)
MustExec(t, "inserting n2", db, "INSERT INTO notes (uuid, book_uuid, usn, added_on, edited_on, body, public, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", n2.UUID, n2.BookUUID, n2.USN, n2.AddedOn, n2.EditedOn, n2.Body, n2.Public, n2.Deleted, n2.Dirty)
// execute
tx, err := db.Begin()
@ -463,24 +458,24 @@ func TestNoteExpunge(t *testing.T) {
// test
var noteCount int
testutils.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
testutils.AssertEqualf(t, noteCount, 1, "note count mismatch")
assert.Equalf(t, noteCount, 1, "note count mismatch")
var n2Record Note
testutils.MustScan(t, "getting n2",
MustScan(t, "getting n2",
db.QueryRow("SELECT uuid, book_uuid, body, added_on, edited_on, usn, public, deleted, dirty FROM notes WHERE uuid = ?", n2.UUID),
&n2Record.UUID, &n2Record.BookUUID, &n2Record.Body, &n2Record.AddedOn, &n2Record.EditedOn, &n2Record.USN, &n2Record.Public, &n2Record.Deleted, &n2Record.Dirty)
testutils.AssertEqual(t, n2Record.UUID, n2.UUID, "n2 uuid mismatch")
testutils.AssertEqual(t, n2Record.BookUUID, n2.BookUUID, "n2 bookUUID mismatch")
testutils.AssertEqual(t, n2Record.Body, n2.Body, "n2 body mismatch")
testutils.AssertEqual(t, n2Record.AddedOn, n2.AddedOn, "n2 addedOn mismatch")
testutils.AssertEqual(t, n2Record.EditedOn, n2.EditedOn, "n2 editedOn mismatch")
testutils.AssertEqual(t, n2Record.USN, n2.USN, "n2 usn mismatch")
testutils.AssertEqual(t, n2Record.Public, n2.Public, "n2 public mismatch")
testutils.AssertEqual(t, n2Record.Deleted, n2.Deleted, "n2 deleted mismatch")
testutils.AssertEqual(t, n2Record.Dirty, n2.Dirty, "n2 dirty mismatch")
assert.Equal(t, n2Record.UUID, n2.UUID, "n2 uuid mismatch")
assert.Equal(t, n2Record.BookUUID, n2.BookUUID, "n2 bookUUID mismatch")
assert.Equal(t, n2Record.Body, n2.Body, "n2 body mismatch")
assert.Equal(t, n2Record.AddedOn, n2.AddedOn, "n2 addedOn mismatch")
assert.Equal(t, n2Record.EditedOn, n2.EditedOn, "n2 editedOn mismatch")
assert.Equal(t, n2Record.USN, n2.USN, "n2 usn mismatch")
assert.Equal(t, n2Record.Public, n2.Public, "n2 public mismatch")
assert.Equal(t, n2Record.Deleted, n2.Deleted, "n2 deleted mismatch")
assert.Equal(t, n2Record.Dirty, n2.Dirty, "n2 dirty mismatch")
}
func TestNewBook(t *testing.T) {
@ -510,11 +505,11 @@ func TestNewBook(t *testing.T) {
for idx, tc := range testCases {
got := NewBook(tc.uuid, tc.label, tc.usn, tc.deleted, tc.dirty)
testutils.AssertEqual(t, got.UUID, tc.uuid, fmt.Sprintf("UUID mismatch for test case %d", idx))
testutils.AssertEqual(t, got.Label, tc.label, fmt.Sprintf("Label mismatch for test case %d", idx))
testutils.AssertEqual(t, got.USN, tc.usn, fmt.Sprintf("USN mismatch for test case %d", idx))
testutils.AssertEqual(t, got.Deleted, tc.deleted, fmt.Sprintf("Deleted mismatch for test case %d", idx))
testutils.AssertEqual(t, got.Dirty, tc.dirty, fmt.Sprintf("Dirty mismatch for test case %d", idx))
assert.Equal(t, got.UUID, tc.uuid, fmt.Sprintf("UUID mismatch for test case %d", idx))
assert.Equal(t, got.Label, tc.label, fmt.Sprintf("Label mismatch for test case %d", idx))
assert.Equal(t, got.USN, tc.usn, fmt.Sprintf("USN mismatch for test case %d", idx))
assert.Equal(t, got.Deleted, tc.deleted, fmt.Sprintf("Deleted mismatch for test case %d", idx))
assert.Equal(t, got.Dirty, tc.dirty, fmt.Sprintf("Dirty mismatch for test case %d", idx))
}
}
@ -545,8 +540,8 @@ func TestBookInsert(t *testing.T) {
for idx, tc := range testCases {
func() {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
b := Book{
UUID: tc.uuid,
@ -557,7 +552,6 @@ func TestBookInsert(t *testing.T) {
}
// execute
db := ctx.DB
tx, err := db.Begin()
if err != nil {
@ -575,15 +569,15 @@ func TestBookInsert(t *testing.T) {
var uuid, label string
var usn int
var deleted, dirty bool
testutils.MustScan(t, "getting b1",
MustScan(t, "getting b1",
db.QueryRow("SELECT uuid, label, usn, deleted, dirty FROM books WHERE uuid = ?", tc.uuid),
&uuid, &label, &usn, &deleted, &dirty)
testutils.AssertEqual(t, uuid, tc.uuid, fmt.Sprintf("uuid mismatch for test case %d", idx))
testutils.AssertEqual(t, label, tc.label, fmt.Sprintf("label mismatch for test case %d", idx))
testutils.AssertEqual(t, usn, tc.usn, fmt.Sprintf("usn mismatch for test case %d", idx))
testutils.AssertEqual(t, deleted, tc.deleted, fmt.Sprintf("deleted mismatch for test case %d", idx))
testutils.AssertEqual(t, dirty, tc.dirty, fmt.Sprintf("dirty mismatch for test case %d", idx))
assert.Equal(t, uuid, tc.uuid, fmt.Sprintf("uuid mismatch for test case %d", idx))
assert.Equal(t, label, tc.label, fmt.Sprintf("label mismatch for test case %d", idx))
assert.Equal(t, usn, tc.usn, fmt.Sprintf("usn mismatch for test case %d", idx))
assert.Equal(t, deleted, tc.deleted, fmt.Sprintf("deleted mismatch for test case %d", idx))
assert.Equal(t, dirty, tc.dirty, fmt.Sprintf("dirty mismatch for test case %d", idx))
}()
}
}
@ -627,8 +621,8 @@ func TestBookUpdate(t *testing.T) {
for idx, tc := range testCases {
func() {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
b1 := Book{
UUID: "b1-uuid",
@ -645,9 +639,8 @@ func TestBookUpdate(t *testing.T) {
Dirty: false,
}
db := ctx.DB
testutils.MustExec(t, fmt.Sprintf("inserting b1 for test case %d", idx), db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b1.UUID, b1.Label, b1.USN, b1.Deleted, b1.Dirty)
testutils.MustExec(t, fmt.Sprintf("inserting b2 for test case %d", idx), db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b2.UUID, b2.Label, b2.USN, b2.Deleted, b2.Dirty)
MustExec(t, fmt.Sprintf("inserting b1 for test case %d", idx), db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b1.UUID, b1.Label, b1.USN, b1.Deleted, b1.Dirty)
MustExec(t, fmt.Sprintf("inserting b2 for test case %d", idx), db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b2.UUID, b2.Label, b2.USN, b2.Deleted, b2.Dirty)
// execute
tx, err := db.Begin()
@ -669,24 +662,24 @@ func TestBookUpdate(t *testing.T) {
// test
var b1Record, b2Record Book
testutils.MustScan(t, "getting b1",
MustScan(t, "getting b1",
db.QueryRow("SELECT uuid, label, usn, deleted, dirty FROM books WHERE uuid = ?", tc.uuid),
&b1Record.UUID, &b1Record.Label, &b1Record.USN, &b1Record.Deleted, &b1Record.Dirty)
testutils.MustScan(t, "getting b2",
MustScan(t, "getting b2",
db.QueryRow("SELECT uuid, label, usn, deleted, dirty FROM books WHERE uuid = ?", b2.UUID),
&b2Record.UUID, &b2Record.Label, &b2Record.USN, &b2Record.Deleted, &b2Record.Dirty)
testutils.AssertEqual(t, b1Record.UUID, b1.UUID, fmt.Sprintf("b1 uuid mismatch for test case %d", idx))
testutils.AssertEqual(t, b1Record.Label, tc.newLabel, fmt.Sprintf("b1 label mismatch for test case %d", idx))
testutils.AssertEqual(t, b1Record.USN, tc.newUSN, fmt.Sprintf("b1 usn mismatch for test case %d", idx))
testutils.AssertEqual(t, b1Record.Deleted, tc.newDeleted, fmt.Sprintf("b1 deleted mismatch for test case %d", idx))
testutils.AssertEqual(t, b1Record.Dirty, tc.newDirty, fmt.Sprintf("b1 dirty mismatch for test case %d", idx))
assert.Equal(t, b1Record.UUID, b1.UUID, fmt.Sprintf("b1 uuid mismatch for test case %d", idx))
assert.Equal(t, b1Record.Label, tc.newLabel, fmt.Sprintf("b1 label mismatch for test case %d", idx))
assert.Equal(t, b1Record.USN, tc.newUSN, fmt.Sprintf("b1 usn mismatch for test case %d", idx))
assert.Equal(t, b1Record.Deleted, tc.newDeleted, fmt.Sprintf("b1 deleted mismatch for test case %d", idx))
assert.Equal(t, b1Record.Dirty, tc.newDirty, fmt.Sprintf("b1 dirty mismatch for test case %d", idx))
testutils.AssertEqual(t, b2Record.UUID, b2.UUID, fmt.Sprintf("b2 uuid mismatch for test case %d", idx))
testutils.AssertEqual(t, b2Record.Label, b2.Label, fmt.Sprintf("b2 label mismatch for test case %d", idx))
testutils.AssertEqual(t, b2Record.USN, b2.USN, fmt.Sprintf("b2 usn mismatch for test case %d", idx))
testutils.AssertEqual(t, b2Record.Deleted, b2.Deleted, fmt.Sprintf("b2 deleted mismatch for test case %d", idx))
testutils.AssertEqual(t, b2Record.Dirty, b2.Dirty, fmt.Sprintf("b2 dirty mismatch for test case %d", idx))
assert.Equal(t, b2Record.UUID, b2.UUID, fmt.Sprintf("b2 uuid mismatch for test case %d", idx))
assert.Equal(t, b2Record.Label, b2.Label, fmt.Sprintf("b2 label mismatch for test case %d", idx))
assert.Equal(t, b2Record.USN, b2.USN, fmt.Sprintf("b2 usn mismatch for test case %d", idx))
assert.Equal(t, b2Record.Deleted, b2.Deleted, fmt.Sprintf("b2 deleted mismatch for test case %d", idx))
assert.Equal(t, b2Record.Dirty, b2.Dirty, fmt.Sprintf("b2 dirty mismatch for test case %d", idx))
}()
}
}
@ -707,8 +700,8 @@ func TestBookUpdateUUID(t *testing.T) {
t.Run(fmt.Sprintf("testCase%d", idx), func(t *testing.T) {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
b1 := Book{
UUID: "b1-uuid",
@ -725,9 +718,8 @@ func TestBookUpdateUUID(t *testing.T) {
Dirty: false,
}
db := ctx.DB
testutils.MustExec(t, "inserting b1", db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b1.UUID, b1.Label, b1.USN, b1.Deleted, b1.Dirty)
testutils.MustExec(t, "inserting b2", db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b2.UUID, b2.Label, b2.USN, b2.Deleted, b2.Dirty)
MustExec(t, "inserting b1", db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b1.UUID, b1.Label, b1.USN, b1.Deleted, b1.Dirty)
MustExec(t, "inserting b2", db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b2.UUID, b2.Label, b2.USN, b2.Deleted, b2.Dirty)
// execute
tx, err := db.Begin()
@ -743,24 +735,24 @@ func TestBookUpdateUUID(t *testing.T) {
// test
var b1Record, b2Record Book
testutils.MustScan(t, "getting b1",
MustScan(t, "getting b1",
db.QueryRow("SELECT uuid, label, usn, deleted, dirty FROM books WHERE label = ?", "b1-label"),
&b1Record.UUID, &b1Record.Label, &b1Record.USN, &b1Record.Deleted, &b1Record.Dirty)
testutils.MustScan(t, "getting b2",
MustScan(t, "getting b2",
db.QueryRow("SELECT uuid, label, usn, deleted, dirty FROM books WHERE label = ?", "b2-label"),
&b2Record.UUID, &b2Record.Label, &b2Record.USN, &b2Record.Deleted, &b2Record.Dirty)
testutils.AssertEqual(t, b1.UUID, tc.newUUID, "b1 original reference uuid mismatch")
testutils.AssertEqual(t, b1Record.UUID, tc.newUUID, "b1 uuid mismatch")
testutils.AssertEqual(t, b2Record.UUID, b2.UUID, "b2 uuid mismatch")
assert.Equal(t, b1.UUID, tc.newUUID, "b1 original reference uuid mismatch")
assert.Equal(t, b1Record.UUID, tc.newUUID, "b1 uuid mismatch")
assert.Equal(t, b2Record.UUID, b2.UUID, "b2 uuid mismatch")
})
}
}
func TestBookExpunge(t *testing.T) {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
b1 := Book{
UUID: "b1-uuid",
@ -777,9 +769,8 @@ func TestBookExpunge(t *testing.T) {
Dirty: false,
}
db := ctx.DB
testutils.MustExec(t, "inserting b1", db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b1.UUID, b1.Label, b1.USN, b1.Deleted, b1.Dirty)
testutils.MustExec(t, "inserting b2", db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b2.UUID, b2.Label, b2.USN, b2.Deleted, b2.Dirty)
MustExec(t, "inserting b1", db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b1.UUID, b1.Label, b1.USN, b1.Deleted, b1.Dirty)
MustExec(t, "inserting b2", db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b2.UUID, b2.Label, b2.USN, b2.Deleted, b2.Dirty)
// execute
tx, err := db.Begin()
@ -796,27 +787,27 @@ func TestBookExpunge(t *testing.T) {
// test
var bookCount int
testutils.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
testutils.AssertEqualf(t, bookCount, 1, "book count mismatch")
assert.Equalf(t, bookCount, 1, "book count mismatch")
var b2Record Book
testutils.MustScan(t, "getting b2",
MustScan(t, "getting b2",
db.QueryRow("SELECT uuid, label, usn, deleted, dirty FROM books WHERE uuid = ?", "b2-uuid"),
&b2Record.UUID, &b2Record.Label, &b2Record.USN, &b2Record.Deleted, &b2Record.Dirty)
testutils.AssertEqual(t, b2Record.UUID, b2.UUID, "b2 uuid mismatch")
testutils.AssertEqual(t, b2Record.Label, b2.Label, "b2 label mismatch")
testutils.AssertEqual(t, b2Record.USN, b2.USN, "b2 usn mismatch")
testutils.AssertEqual(t, b2Record.Deleted, b2.Deleted, "b2 deleted mismatch")
testutils.AssertEqual(t, b2Record.Dirty, b2.Dirty, "b2 dirty mismatch")
assert.Equal(t, b2Record.UUID, b2.UUID, "b2 uuid mismatch")
assert.Equal(t, b2Record.Label, b2.Label, "b2 label mismatch")
assert.Equal(t, b2Record.USN, b2.USN, "b2 usn mismatch")
assert.Equal(t, b2Record.Deleted, b2.Deleted, "b2 deleted mismatch")
assert.Equal(t, b2Record.Dirty, b2.Dirty, "b2 dirty mismatch")
}
// TestNoteFTS tests that note full text search indices stay in sync with the notes after insert, update and delete
func TestNoteFTS(t *testing.T) {
// set up
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
// execute - insert
n := Note{
@ -830,7 +821,6 @@ func TestNoteFTS(t *testing.T) {
Deleted: false,
Dirty: false,
}
db := ctx.DB
tx, err := db.Begin()
if err != nil {
@ -846,13 +836,13 @@ func TestNoteFTS(t *testing.T) {
// test
var noteCount, noteFtsCount, noteSearchCount int
testutils.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
testutils.MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), &noteFtsCount)
testutils.MustScan(t, "counting search results", db.QueryRow("SELECT count(*) FROM note_fts WHERE note_fts MATCH ?", "foo"), &noteSearchCount)
MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), &noteFtsCount)
MustScan(t, "counting search results", db.QueryRow("SELECT count(*) FROM note_fts WHERE note_fts MATCH ?", "foo"), &noteSearchCount)
testutils.AssertEqual(t, noteCount, 1, "noteCount mismatch")
testutils.AssertEqual(t, noteFtsCount, 1, "noteFtsCount mismatch")
testutils.AssertEqual(t, noteSearchCount, 1, "noteSearchCount mismatch")
assert.Equal(t, noteCount, 1, "noteCount mismatch")
assert.Equal(t, noteFtsCount, 1, "noteFtsCount mismatch")
assert.Equal(t, noteSearchCount, 1, "noteSearchCount mismatch")
// execute - update
tx, err = db.Begin()
@ -869,15 +859,15 @@ func TestNoteFTS(t *testing.T) {
tx.Commit()
// test
testutils.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
testutils.MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), &noteFtsCount)
testutils.AssertEqual(t, noteCount, 1, "noteCount mismatch")
testutils.AssertEqual(t, noteFtsCount, 1, "noteFtsCount mismatch")
MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), &noteFtsCount)
assert.Equal(t, noteCount, 1, "noteCount mismatch")
assert.Equal(t, noteFtsCount, 1, "noteFtsCount mismatch")
testutils.MustScan(t, "counting search results", db.QueryRow("SELECT count(*) FROM note_fts WHERE note_fts MATCH ?", "foo"), &noteSearchCount)
testutils.AssertEqual(t, noteSearchCount, 0, "noteSearchCount for foo mismatch")
testutils.MustScan(t, "counting search results", db.QueryRow("SELECT count(*) FROM note_fts WHERE note_fts MATCH ?", "baz"), &noteSearchCount)
testutils.AssertEqual(t, noteSearchCount, 1, "noteSearchCount for baz mismatch")
MustScan(t, "counting search results", db.QueryRow("SELECT count(*) FROM note_fts WHERE note_fts MATCH ?", "foo"), &noteSearchCount)
assert.Equal(t, noteSearchCount, 0, "noteSearchCount for foo mismatch")
MustScan(t, "counting search results", db.QueryRow("SELECT count(*) FROM note_fts WHERE note_fts MATCH ?", "baz"), &noteSearchCount)
assert.Equal(t, noteSearchCount, 1, "noteSearchCount for baz mismatch")
// execute - delete
tx, err = db.Begin()
@ -893,9 +883,9 @@ func TestNoteFTS(t *testing.T) {
tx.Commit()
// test
testutils.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
testutils.MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), &noteFtsCount)
MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), &noteFtsCount)
testutils.AssertEqual(t, noteCount, 0, "noteCount mismatch")
testutils.AssertEqual(t, noteFtsCount, 0, "noteFtsCount mismatch")
assert.Equal(t, noteCount, 0, "noteCount mismatch")
assert.Equal(t, noteFtsCount, 0, "noteFtsCount mismatch")
}

View file

@ -16,17 +16,25 @@
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package core
package database
import (
"encoding/base64"
"database/sql"
"github.com/dnote/dnote/cli/infra"
"github.com/pkg/errors"
)
// GetSystem scans the given system configuration record onto the destination
func GetSystem(db *DB, key string, dest interface{}) error {
if err := db.QueryRow("SELECT value FROM system WHERE key = ?", key).Scan(dest); err != nil {
return errors.Wrap(err, "finding system configuration record")
}
return nil
}
// InsertSystem inserets a system configuration
func InsertSystem(db *infra.DB, key, val string) error {
func InsertSystem(db *DB, key, val string) error {
if _, err := db.Exec("INSERT INTO system (key, value) VALUES (? , ?);", key, val); err != nil {
return errors.Wrap(err, "saving system config")
}
@ -35,7 +43,7 @@ func InsertSystem(db *infra.DB, key, val string) error {
}
// UpsertSystem inserts or updates a system configuration
func UpsertSystem(db *infra.DB, key, val string) error {
func UpsertSystem(db *DB, key, val string) error {
var count int
if err := db.QueryRow("SELECT count(*) FROM system WHERE key = ?", key).Scan(&count); err != nil {
return errors.Wrap(err, "counting system record")
@ -55,7 +63,7 @@ func UpsertSystem(db *infra.DB, key, val string) error {
}
// UpdateSystem updates a system configuration
func UpdateSystem(db *infra.DB, key, val interface{}) error {
func UpdateSystem(db *DB, key, val interface{}) error {
if _, err := db.Exec("UPDATE system SET value = ? WHERE key = ?", val, key); err != nil {
return errors.Wrap(err, "updating system config")
}
@ -63,17 +71,8 @@ func UpdateSystem(db *infra.DB, key, val interface{}) error {
return nil
}
// GetSystem scans the given system configuration record onto the destination
func GetSystem(db *infra.DB, key string, dest interface{}) error {
if err := db.QueryRow("SELECT value FROM system WHERE key = ?", key).Scan(dest); err != nil {
return errors.Wrap(err, "finding system configuration record")
}
return nil
}
// DeleteSystem delets the given system record
func DeleteSystem(db *infra.DB, key string) error {
func DeleteSystem(db *DB, key string) error {
if _, err := db.Exec("DELETE FROM system WHERE key = ?", key); err != nil {
return errors.Wrap(err, "deleting system config")
}
@ -81,23 +80,44 @@ func DeleteSystem(db *infra.DB, key string) error {
return nil
}
// GetCipherKey retrieves the cipher key and decode the base64 into bytes.
func GetCipherKey(ctx infra.DnoteCtx) ([]byte, error) {
db, err := ctx.DB.Begin()
if err != nil {
return nil, errors.Wrap(err, "beginning transaction")
}
var cipherKeyB64 string
err = GetSystem(db, infra.SystemCipherKey, &cipherKeyB64)
if err != nil {
return []byte{}, errors.Wrap(err, "getting enc key")
}
cipherKey, err := base64.StdEncoding.DecodeString(cipherKeyB64)
if err != nil {
return nil, errors.Wrap(err, "decoding cipherKey from base64")
}
return cipherKey, nil
// NoteInfo is a basic information about a note
type NoteInfo struct {
RowID int
BookLabel string
UUID string
Content string
AddedOn int64
EditedOn int64
}
// GetNoteInfo returns a NoteInfo for the note with the given noteRowID
func GetNoteInfo(db *DB, noteRowID string) (NoteInfo, error) {
var ret NoteInfo
err := db.QueryRow(`SELECT books.label, notes.uuid, notes.body, notes.added_on, notes.edited_on, notes.rowid
FROM notes
INNER JOIN books ON books.uuid = notes.book_uuid
WHERE notes.rowid = ? AND notes.deleted = false`, noteRowID).
Scan(&ret.BookLabel, &ret.UUID, &ret.Content, &ret.AddedOn, &ret.EditedOn, &ret.RowID)
if err == sql.ErrNoRows {
return ret, errors.Errorf("note %s not found", noteRowID)
} else if err != nil {
return ret, errors.Wrap(err, "querying the note")
}
return ret, nil
}
// GetBookUUID returns a uuid of a book given a label
func GetBookUUID(db *DB, label string) (string, error) {
var ret string
err := db.QueryRow("SELECT uuid FROM books WHERE label = ?", label).Scan(&ret)
if err == sql.ErrNoRows {
return ret, errors.Errorf("book '%s' not found", label)
} else if err != nil {
return ret, errors.Wrap(err, "querying the book")
}
return ret, nil
}

View file

@ -16,13 +16,13 @@
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package core
package database
import (
"fmt"
"testing"
"github.com/dnote/dnote/cli/testutils"
"github.com/dnote/dnote/pkg/assert"
"github.com/pkg/errors"
)
@ -44,12 +44,10 @@ func TestInsertSystem(t *testing.T) {
for _, tc := range testCases {
t.Run(fmt.Sprintf("insert %s %s", tc.key, tc.val), func(t *testing.T) {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
// execute
db := ctx.DB
tx, err := db.Begin()
if err != nil {
t.Fatalf(errors.Wrap(err, "beginning a transaction").Error())
@ -64,11 +62,11 @@ func TestInsertSystem(t *testing.T) {
// test
var key, val string
testutils.MustScan(t, "getting the saved record",
MustScan(t, "getting the saved record",
db.QueryRow("SELECT key, value FROM system WHERE key = ?", tc.key), &key, &val)
testutils.AssertEqual(t, key, tc.key, "key mismatch for test case")
testutils.AssertEqual(t, val, tc.val, "val mismatch for test case")
assert.Equal(t, key, tc.key, "key mismatch for test case")
assert.Equal(t, val, tc.val, "val mismatch for test case")
})
}
}
@ -94,14 +92,13 @@ func TestUpsertSystem(t *testing.T) {
for _, tc := range testCases {
t.Run(fmt.Sprintf("insert %s %s", tc.key, tc.val), func(t *testing.T) {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
db := ctx.DB
testutils.MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "baz", "quz")
MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "baz", "quz")
var initialSystemCount int
testutils.MustScan(t, "counting records", db.QueryRow("SELECT count(*) FROM system"), &initialSystemCount)
MustScan(t, "counting records", db.QueryRow("SELECT count(*) FROM system"), &initialSystemCount)
// execute
tx, err := db.Begin()
@ -118,15 +115,15 @@ func TestUpsertSystem(t *testing.T) {
// test
var key, val string
testutils.MustScan(t, "getting the saved record",
MustScan(t, "getting the saved record",
db.QueryRow("SELECT key, value FROM system WHERE key = ?", tc.key), &key, &val)
var systemCount int
testutils.MustScan(t, "counting records",
MustScan(t, "counting records",
db.QueryRow("SELECT count(*) FROM system"), &systemCount)
testutils.AssertEqual(t, key, tc.key, "key mismatch")
testutils.AssertEqual(t, val, tc.val, "val mismatch")
testutils.AssertEqual(t, systemCount, initialSystemCount+tc.countDelta, "count mismatch")
assert.Equal(t, key, tc.key, "key mismatch")
assert.Equal(t, val, tc.val, "val mismatch")
assert.Equal(t, systemCount, initialSystemCount+tc.countDelta, "count mismatch")
})
}
}
@ -134,12 +131,11 @@ func TestUpsertSystem(t *testing.T) {
func TestGetSystem(t *testing.T) {
t.Run(fmt.Sprintf("get string value"), func(t *testing.T) {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
// execute
db := ctx.DB
testutils.MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "foo", "bar")
MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "foo", "bar")
tx, err := db.Begin()
if err != nil {
@ -153,17 +149,16 @@ func TestGetSystem(t *testing.T) {
tx.Commit()
// test
testutils.AssertEqual(t, dest, "bar", "dest mismatch")
assert.Equal(t, dest, "bar", "dest mismatch")
})
t.Run(fmt.Sprintf("get int64 value"), func(t *testing.T) {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
// execute
db := ctx.DB
testutils.MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "foo", 1234)
MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "foo", 1234)
tx, err := db.Begin()
if err != nil {
@ -177,7 +172,7 @@ func TestGetSystem(t *testing.T) {
tx.Commit()
// test
testutils.AssertEqual(t, dest, int64(1234), "dest mismatch")
assert.Equal(t, dest, int64(1234), "dest mismatch")
})
}
@ -200,15 +195,14 @@ func TestUpdateSystem(t *testing.T) {
for _, tc := range testCases {
t.Run(fmt.Sprintf("update %s %s", tc.key, tc.val), func(t *testing.T) {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := InitTestDB(t, "../tmp/dnote-test.db", nil)
defer CloseTestDB(t, db)
db := ctx.DB
testutils.MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "foo", "fuz")
testutils.MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "baz", "quz")
MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "foo", "fuz")
MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "baz", "quz")
var initialSystemCount int
testutils.MustScan(t, "counting records", db.QueryRow("SELECT count(*) FROM system"), &initialSystemCount)
MustScan(t, "counting records", db.QueryRow("SELECT count(*) FROM system"), &initialSystemCount)
// execute
tx, err := db.Begin()
@ -225,15 +219,15 @@ func TestUpdateSystem(t *testing.T) {
// test
var key, val string
testutils.MustScan(t, "getting the saved record",
MustScan(t, "getting the saved record",
db.QueryRow("SELECT key, value FROM system WHERE key = ?", tc.key), &key, &val)
var systemCount int
testutils.MustScan(t, "counting records",
MustScan(t, "counting records",
db.QueryRow("SELECT count(*) FROM system"), &systemCount)
testutils.AssertEqual(t, key, tc.key, "key mismatch")
testutils.AssertEqual(t, val, tc.val, "val mismatch")
testutils.AssertEqual(t, systemCount, initialSystemCount, "count mismatch")
assert.Equal(t, key, tc.key, "key mismatch")
assert.Equal(t, val, tc.val, "val mismatch")
assert.Equal(t, systemCount, initialSystemCount, "count mismatch")
})
}
}

View file

@ -16,12 +16,14 @@
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package infra
package database
import (
"database/sql"
"github.com/pkg/errors"
// use sqlite
_ "github.com/mattn/go-sqlite3"
)
// SQLCommon is the minimal interface required by a db connection
@ -45,27 +47,8 @@ type sqlTx interface {
// DB contains information about the current database connection
type DB struct {
Conn SQLCommon
}
// OpenDB initializes a new connection to the sqlite database
func OpenDB(dbPath string) (*DB, error) {
dbConn, err := sql.Open("sqlite3", dbPath)
if err != nil {
return nil, errors.Wrap(err, "opening db connection")
}
// Send a ping to ensure that the connection is established
// if err := dbConn.Ping(); err != nil {
// dbConn.Close()
// return nil, errors.Wrap(err, "ping")
// }
db := &DB{
Conn: dbConn,
}
return db, nil
Conn SQLCommon
Filepath string
}
// Begin begins a transaction
@ -136,3 +119,18 @@ func (d *DB) Close() error {
return errors.New("can't close db")
}
// Open initializes a new connection to the sqlite database
func Open(dbPath string) (*DB, error) {
dbConn, err := sql.Open("sqlite3", dbPath)
if err != nil {
return nil, errors.Wrap(err, "opening db connection")
}
db := &DB{
Conn: dbConn,
Filepath: dbPath,
}
return db, nil
}

View file

@ -0,0 +1,170 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package database
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/utils"
"github.com/pkg/errors"
)
var defaultSchemaSQL = `CREATE TABLE books
(
uuid text PRIMARY KEY,
label text NOT NULL
, dirty bool DEFAULT false, usn int DEFAULT 0 NOT NULL, deleted bool DEFAULT false);
CREATE TABLE system
(
key string NOT NULL,
value text NOT NULL
);
CREATE UNIQUE INDEX idx_books_label ON books(label);
CREATE UNIQUE INDEX idx_books_uuid ON books(uuid);
CREATE TABLE IF NOT EXISTS "notes"
(
uuid text NOT NULL,
book_uuid text NOT NULL,
body text NOT NULL,
added_on integer NOT NULL,
edited_on integer DEFAULT 0,
public bool DEFAULT false,
dirty bool DEFAULT false,
usn int DEFAULT 0 NOT NULL,
deleted bool DEFAULT false
);
CREATE VIRTUAL TABLE note_fts USING fts5(content=notes, body, tokenize="porter unicode61 categories 'L* N* Co Ps Pe'")
/* note_fts(body) */;
CREATE TABLE IF NOT EXISTS 'note_fts_data'(id INTEGER PRIMARY KEY, block BLOB);
CREATE TABLE IF NOT EXISTS 'note_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS 'note_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB);
CREATE TABLE IF NOT EXISTS 'note_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID;
CREATE TRIGGER notes_after_insert AFTER INSERT ON notes BEGIN
INSERT INTO note_fts(rowid, body) VALUES (new.rowid, new.body);
END;
CREATE TRIGGER notes_after_delete AFTER DELETE ON notes BEGIN
INSERT INTO note_fts(note_fts, rowid, body) VALUES ('delete', old.rowid, old.body);
END;
CREATE TRIGGER notes_after_update AFTER UPDATE ON notes BEGIN
INSERT INTO note_fts(note_fts, rowid, body) VALUES ('delete', old.rowid, old.body);
INSERT INTO note_fts(rowid, body) VALUES (new.rowid, new.body);
END;
CREATE TABLE actions
(
uuid text PRIMARY KEY,
schema integer NOT NULL,
type text NOT NULL,
data text NOT NULL,
timestamp integer NOT NULL
);
CREATE UNIQUE INDEX idx_notes_uuid ON notes(uuid);
CREATE INDEX idx_notes_book_uuid ON notes(book_uuid);`
// MustScan scans the given row and fails a test in case of any errors
func MustScan(t *testing.T, message string, row *sql.Row, args ...interface{}) {
err := row.Scan(args...)
if err != nil {
t.Fatal(errors.Wrap(errors.Wrap(err, "scanning a row"), message))
}
}
// MustExec executes the given SQL query and fails a test if an error occurs
func MustExec(t *testing.T, message string, db *DB, query string, args ...interface{}) sql.Result {
result, err := db.Exec(query, args...)
if err != nil {
t.Fatal(errors.Wrap(errors.Wrap(err, "executing sql"), message))
}
return result
}
// TestDBOptions contains options for test database
type TestDBOptions struct {
SchemaSQLPath string
SkipMigration bool
}
// InitTestDB opens a test database connection
func InitTestDB(t *testing.T, dbPath string, options *TestDBOptions) *DB {
db, err := Open(dbPath)
if err != nil {
t.Fatal(errors.Wrap(err, "opening database connection"))
}
dir, _ := filepath.Split(dbPath)
err = os.MkdirAll(dir, 0777)
if err != nil {
t.Fatal(errors.Wrap(err, "creating the directory for test database file"))
}
var schemaSQL string
if options != nil && options.SchemaSQLPath != "" {
b := utils.ReadFileAbs(options.SchemaSQLPath)
schemaSQL = string(b)
} else {
schemaSQL = defaultSchemaSQL
}
if _, err := db.Exec(schemaSQL); err != nil {
t.Fatal(errors.Wrap(err, "running schema sql"))
}
if options == nil || !options.SkipMigration {
MarkMigrationComplete(t, db)
}
return db
}
// CloseTestDB closes the test database
func CloseTestDB(t *testing.T, db *DB) {
if err := db.Close(); err != nil {
t.Fatal(errors.Wrap(err, "closing database"))
}
if err := os.RemoveAll(db.Filepath); err != nil {
t.Fatal(errors.Wrap(err, "removing database file"))
}
}
// OpenTestDB opens the database connection to the test database
func OpenTestDB(t *testing.T, dnoteDir string) *DB {
dbPath := fmt.Sprintf("%s/%s", dnoteDir, consts.DnoteDBFileName)
db, err := Open(dbPath)
if err != nil {
t.Fatal(errors.Wrap(err, "opening database connection to the test database"))
}
return db
}
// MarkMigrationComplete marks all migrations as complete in the database
func MarkMigrationComplete(t *testing.T, db *DB) {
if _, err := db.Exec("INSERT INTO system (key, value) VALUES (? , ?);", consts.SystemSchema, 11); err != nil {
t.Fatal(errors.Wrap(err, "inserting schema"))
}
if _, err := db.Exec("INSERT INTO system (key, value) VALUES (? , ?);", consts.SystemRemoteSchema, 1); err != nil {
t.Fatal(errors.Wrap(err, "inserting remote schema"))
}
}

56
pkg/cli/infra/config.go Normal file
View file

@ -0,0 +1,56 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package infra
import (
"fmt"
"io/ioutil"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
)
var (
// ConfigFilename is the name of the config file
ConfigFilename = "dnoterc"
)
// GetConfigPath returns the path to the dnote config file
func GetConfigPath(ctx context.DnoteCtx) string {
return fmt.Sprintf("%s/%s", ctx.DnoteDir, ConfigFilename)
}
// ReadConfig reads the config file
func ReadConfig(ctx context.DnoteCtx) (Config, error) {
var ret Config
configPath := GetConfigPath(ctx)
b, err := ioutil.ReadFile(configPath)
if err != nil {
return ret, errors.Wrap(err, "reading config file")
}
err = yaml.Unmarshal(b, &ret)
if err != nil {
return ret, errors.Wrap(err, "unmarshalling config")
}
return ret, nil
}

365
pkg/cli/infra/init.go Normal file
View file

@ -0,0 +1,365 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
// Package infra provides operations and definitions for the
// local infrastructure for Dnote
package infra
import (
"database/sql"
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"os/user"
"strconv"
"time"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/dnote/dnote/pkg/cli/migrate"
"github.com/dnote/dnote/pkg/cli/utils"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)
// Config holds dnote configuration
type Config struct {
Editor string
}
// RunEFunc is a function type of dnote commands
type RunEFunc func(*cobra.Command, []string) error
func newCtx(apiEndpoint, versionTag string) (context.DnoteCtx, error) {
homeDir, err := getHomeDir()
if err != nil {
return context.DnoteCtx{}, errors.Wrap(err, "Failed to get home dir")
}
dnoteDir := getDnoteDir(homeDir)
dnoteDBPath := fmt.Sprintf("%s/%s", dnoteDir, consts.DnoteDBFileName)
db, err := database.Open(dnoteDBPath)
if err != nil {
return context.DnoteCtx{}, errors.Wrap(err, "conntecting to db")
}
ctx := context.DnoteCtx{
HomeDir: homeDir,
DnoteDir: dnoteDir,
APIEndpoint: apiEndpoint,
Version: versionTag,
DB: db,
}
return ctx, nil
}
// Init initializes the Dnote environment and returns a new dnote context
func Init(apiEndpoint, versionTag string) (*context.DnoteCtx, error) {
ctx, err := newCtx(apiEndpoint, versionTag)
if err != nil {
return nil, errors.Wrap(err, "initializing a context")
}
if err := InitFiles(ctx); err != nil {
return nil, errors.Wrap(err, "initializing files")
}
if err := InitDB(ctx); err != nil {
return nil, errors.Wrap(err, "initializing database")
}
if err := InitSystem(ctx); err != nil {
return nil, errors.Wrap(err, "initializing system data")
}
if err := migrate.Legacy(ctx); err != nil {
return nil, errors.Wrap(err, "running legacy migration")
}
if err := migrate.Run(ctx, migrate.LocalSequence, migrate.LocalMode); err != nil {
return nil, errors.Wrap(err, "running migration")
}
ctx, err = SetupCtx(ctx)
if err != nil {
return nil, errors.Wrap(err, "setting up the context")
}
return &ctx, nil
}
// SetupCtx populates context and returns a new context
func SetupCtx(ctx context.DnoteCtx) (context.DnoteCtx, error) {
db := ctx.DB
var sessionKey, cipherKeyB64 string
var sessionKeyExpiry int64
err := db.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemSessionKey).Scan(&sessionKey)
if err != nil && err != sql.ErrNoRows {
return ctx, errors.Wrap(err, "finding sesison key")
}
err = db.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemCipherKey).Scan(&cipherKeyB64)
if err != nil && err != sql.ErrNoRows {
return ctx, errors.Wrap(err, "finding sesison key")
}
err = db.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemSessionKeyExpiry).Scan(&sessionKeyExpiry)
if err != nil && err != sql.ErrNoRows {
return ctx, errors.Wrap(err, "finding sesison key expiry")
}
cipherKey, err := base64.StdEncoding.DecodeString(cipherKeyB64)
if err != nil {
return ctx, errors.Wrap(err, "decoding cipherKey from base64")
}
ret := context.DnoteCtx{
HomeDir: ctx.HomeDir,
DnoteDir: ctx.DnoteDir,
APIEndpoint: ctx.APIEndpoint,
Version: ctx.Version,
DB: ctx.DB,
SessionKey: sessionKey,
SessionKeyExpiry: sessionKeyExpiry,
CipherKey: cipherKey,
}
return ret, nil
}
func getDnoteDir(homeDir string) string {
var ret string
dnoteDirEnv := os.Getenv("DNOTE_DIR")
if dnoteDirEnv == "" {
ret = fmt.Sprintf("%s/%s", homeDir, consts.DnoteDirName)
} else {
ret = dnoteDirEnv
}
return ret
}
func getHomeDir() (string, error) {
homeDirEnv := os.Getenv("DNOTE_HOME_DIR")
if homeDirEnv != "" {
return homeDirEnv, nil
}
usr, err := user.Current()
if err != nil {
return "", errors.Wrap(err, "Failed to get current user")
}
return usr.HomeDir, nil
}
// InitDB initializes the database.
// Ideally this process must be a part of migration sequence. But it is performed
// seaprately because it is a prerequisite for legacy migration.
func InitDB(ctx context.DnoteCtx) error {
log.Debug("initializing the database\n")
db := ctx.DB
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS notes
(
id integer PRIMARY KEY AUTOINCREMENT,
uuid text NOT NULL,
book_uuid text NOT NULL,
content text NOT NULL,
added_on integer NOT NULL,
edited_on integer DEFAULT 0,
public bool DEFAULT false
)`)
if err != nil {
return errors.Wrap(err, "creating notes table")
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS books
(
uuid text PRIMARY KEY,
label text NOT NULL
)`)
if err != nil {
return errors.Wrap(err, "creating books table")
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS system
(
key string NOT NULL,
value text NOT NULL
)`)
if err != nil {
return errors.Wrap(err, "creating system table")
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS actions
(
uuid text PRIMARY KEY,
schema integer NOT NULL,
type text NOT NULL,
data text NOT NULL,
timestamp integer NOT NULL
)`)
if err != nil {
return errors.Wrap(err, "creating actions table")
}
_, err = db.Exec(`
CREATE UNIQUE INDEX IF NOT EXISTS idx_books_label ON books(label);
CREATE UNIQUE INDEX IF NOT EXISTS idx_notes_uuid ON notes(uuid);
CREATE UNIQUE INDEX IF NOT EXISTS idx_books_uuid ON books(uuid);
CREATE INDEX IF NOT EXISTS idx_notes_book_uuid ON notes(book_uuid);`)
if err != nil {
return errors.Wrap(err, "creating indices")
}
return nil
}
func initSystemKV(db *database.DB, key string, val string) error {
var count int
if err := db.QueryRow("SELECT count(*) FROM system WHERE key = ?", key).Scan(&count); err != nil {
return errors.Wrapf(err, "counting %s", key)
}
if count > 0 {
return nil
}
if _, err := db.Exec("INSERT INTO system (key, value) VALUES (?, ?)", key, val); err != nil {
db.Rollback()
return errors.Wrapf(err, "inserting %s %s", key, val)
}
return nil
}
// InitSystem inserts system data if missing
func InitSystem(ctx context.DnoteCtx) error {
log.Debug("initializing the system\n")
db := ctx.DB
tx, err := db.Begin()
if err != nil {
return errors.Wrap(err, "beginning a transaction")
}
nowStr := strconv.FormatInt(time.Now().Unix(), 10)
if err := initSystemKV(tx, consts.SystemLastUpgrade, nowStr); err != nil {
return errors.Wrapf(err, "initializing system config for %s", consts.SystemLastUpgrade)
}
if err := initSystemKV(tx, consts.SystemLastMaxUSN, "0"); err != nil {
return errors.Wrapf(err, "initializing system config for %s", consts.SystemLastMaxUSN)
}
if err := initSystemKV(tx, consts.SystemLastSyncAt, "0"); err != nil {
return errors.Wrapf(err, "initializing system config for %s", consts.SystemLastSyncAt)
}
tx.Commit()
return nil
}
// getEditorCommand returns the system's editor command with appropriate flags,
// if necessary, to make the command wait until editor is close to exit.
func getEditorCommand() string {
editor := os.Getenv("EDITOR")
var ret string
switch editor {
case "atom":
ret = "atom -w"
case "subl":
ret = "subl -n -w"
case "mate":
ret = "mate -w"
case "vim":
ret = "vim"
case "nano":
ret = "nano"
case "emacs":
ret = "emacs"
case "nvim":
ret = "nvim"
default:
ret = "vi"
}
return ret
}
// initDnoteDir initializes dnote directory if it does not exist yet
func initDnoteDir(ctx context.DnoteCtx) error {
path := ctx.DnoteDir
if utils.FileExists(path) {
return nil
}
if err := os.MkdirAll(path, 0755); err != nil {
return errors.Wrap(err, "Failed to create dnote directory")
}
return nil
}
// initConfigFile populates a new config file if it does not exist yet
func initConfigFile(ctx context.DnoteCtx) error {
path := GetConfigPath(ctx)
if utils.FileExists(path) {
return nil
}
editor := getEditorCommand()
config := Config{
Editor: editor,
}
b, err := yaml.Marshal(config)
if err != nil {
return errors.Wrap(err, "marshalling config into YAML")
}
err = ioutil.WriteFile(path, b, 0644)
if err != nil {
return errors.Wrap(err, "writing the config file")
}
return nil
}
// InitFiles creates, if necessary, the dnote directory and files inside
func InitFiles(ctx context.DnoteCtx) error {
if err := initDnoteDir(ctx); err != nil {
return errors.Wrap(err, "creating the dnote dir")
}
if err := initConfigFile(ctx); err != nil {
return errors.Wrap(err, "generating the config file")
}
return nil
}

View file

@ -16,24 +16,23 @@
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package core
package infra
import (
"testing"
"github.com/dnote/dnote/cli/testutils"
"github.com/dnote/dnote/pkg/assert"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/pkg/errors"
)
func TestInitSystemKV(t *testing.T) {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := ctx.DB
db := database.InitTestDB(t, "../tmp/dnote-test.db", nil)
defer database.CloseTestDB(t, db)
var originalCount int
testutils.MustScan(t, "counting system configs", db.QueryRow("SELECT count(*) FROM system"), &originalCount)
database.MustScan(t, "counting system configs", db.QueryRow("SELECT count(*) FROM system"), &originalCount)
// Execute
tx, err := db.Begin()
@ -50,25 +49,24 @@ func TestInitSystemKV(t *testing.T) {
// Test
var count int
testutils.MustScan(t, "counting system configs", db.QueryRow("SELECT count(*) FROM system"), &count)
testutils.AssertEqual(t, count, originalCount+1, "system count mismatch")
database.MustScan(t, "counting system configs", db.QueryRow("SELECT count(*) FROM system"), &count)
assert.Equal(t, count, originalCount+1, "system count mismatch")
var val string
testutils.MustScan(t, "getting system value",
database.MustScan(t, "getting system value",
db.QueryRow("SELECT value FROM system WHERE key = ?", "testKey"), &val)
testutils.AssertEqual(t, val, "testVal", "system value mismatch")
assert.Equal(t, val, "testVal", "system value mismatch")
}
func TestInitSystemKV_existing(t *testing.T) {
// Setup
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
db := database.InitTestDB(t, "../tmp/dnote-test.db", nil)
defer database.CloseTestDB(t, db)
db := ctx.DB
testutils.MustExec(t, "inserting a system config", db, "INSERT INTO system (key, value) VALUES (?, ?)", "testKey", "testVal")
database.MustExec(t, "inserting a system config", db, "INSERT INTO system (key, value) VALUES (?, ?)", "testKey", "testVal")
var originalCount int
testutils.MustScan(t, "counting system configs", db.QueryRow("SELECT count(*) FROM system"), &originalCount)
database.MustScan(t, "counting system configs", db.QueryRow("SELECT count(*) FROM system"), &originalCount)
// Execute
tx, err := db.Begin()
@ -85,11 +83,11 @@ func TestInitSystemKV_existing(t *testing.T) {
// Test
var count int
testutils.MustScan(t, "counting system configs", db.QueryRow("SELECT count(*) FROM system"), &count)
testutils.AssertEqual(t, count, originalCount, "system count mismatch")
database.MustScan(t, "counting system configs", db.QueryRow("SELECT count(*) FROM system"), &count)
assert.Equal(t, count, originalCount, "system count mismatch")
var val string
testutils.MustScan(t, "getting system value",
database.MustScan(t, "getting system value",
db.QueryRow("SELECT value FROM system WHERE key = ?", "testKey"), &val)
testutils.AssertEqual(t, val, "testVal", "system value should not have been updated")
assert.Equal(t, val, "testVal", "system value should not have been updated")
}

71
pkg/cli/main.go Normal file
View file

@ -0,0 +1,71 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"os"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
_ "github.com/mattn/go-sqlite3"
"github.com/pkg/errors"
// commands
"github.com/dnote/dnote/pkg/cli/cmd/add"
"github.com/dnote/dnote/pkg/cli/cmd/cat"
"github.com/dnote/dnote/pkg/cli/cmd/edit"
"github.com/dnote/dnote/pkg/cli/cmd/find"
"github.com/dnote/dnote/pkg/cli/cmd/login"
"github.com/dnote/dnote/pkg/cli/cmd/logout"
"github.com/dnote/dnote/pkg/cli/cmd/ls"
"github.com/dnote/dnote/pkg/cli/cmd/remove"
"github.com/dnote/dnote/pkg/cli/cmd/root"
"github.com/dnote/dnote/pkg/cli/cmd/sync"
"github.com/dnote/dnote/pkg/cli/cmd/version"
"github.com/dnote/dnote/pkg/cli/cmd/view"
)
// apiEndpoint and versionTag are populated during link time
var apiEndpoint string
var versionTag = "master"
func main() {
ctx, err := infra.Init(apiEndpoint, versionTag)
if err != nil {
panic(errors.Wrap(err, "initializing context"))
}
defer ctx.DB.Close()
root.Register(remove.NewCmd(*ctx))
root.Register(edit.NewCmd(*ctx))
root.Register(login.NewCmd(*ctx))
root.Register(logout.NewCmd(*ctx))
root.Register(add.NewCmd(*ctx))
root.Register(ls.NewCmd(*ctx))
root.Register(sync.NewCmd(*ctx))
root.Register(version.NewCmd(*ctx))
root.Register(cat.NewCmd(*ctx))
root.Register(view.NewCmd(*ctx))
root.Register(find.NewCmd(*ctx))
if err := root.Execute(); err != nil {
log.Errorf("%s\n", err.Error())
os.Exit(1)
}
}

333
pkg/cli/main_test.go Normal file
View file

@ -0,0 +1,333 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"log"
"os"
"os/exec"
"testing"
"github.com/dnote/dnote/pkg/assert"
"github.com/dnote/dnote/pkg/cli/consts"
// "github.com/dnote/dnote/pkg/cli/core"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/testutils"
"github.com/dnote/dnote/pkg/cli/utils"
"github.com/pkg/errors"
)
var binaryName = "test-dnote"
var opts = testutils.RunDnoteCmdOptions{
HomeDir: "./tmp",
DnoteDir: "./tmp/.dnote",
}
func TestMain(m *testing.M) {
if err := exec.Command("go", "build", "--tags", "fts5", "-o", binaryName).Run(); err != nil {
log.Print(errors.Wrap(err, "building a binary").Error())
os.Exit(1)
}
os.Exit(m.Run())
}
func TestInit(t *testing.T) {
// Execute
testutils.RunDnoteCmd(t, opts, binaryName)
defer testutils.RemoveDir(t, opts.HomeDir)
db := database.OpenTestDB(t, opts.DnoteDir)
// Test
if !utils.FileExists(opts.DnoteDir) {
t.Errorf("dnote directory was not initialized")
}
if !utils.FileExists(fmt.Sprintf("%s/%s", opts.DnoteDir, infra.ConfigFilename)) {
t.Errorf("config file was not initialized")
}
var notesTableCount, booksTableCount, systemTableCount int
database.MustScan(t, "counting notes",
db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type = ? AND name = ?", "table", "notes"), &notesTableCount)
database.MustScan(t, "counting books",
db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type = ? AND name = ?", "table", "books"), &booksTableCount)
database.MustScan(t, "counting system",
db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type = ? AND name = ?", "table", "system"), &systemTableCount)
assert.Equal(t, notesTableCount, 1, "notes table count mismatch")
assert.Equal(t, booksTableCount, 1, "books table count mismatch")
assert.Equal(t, systemTableCount, 1, "system table count mismatch")
// test that all default system configurations are generated
var lastUpgrade, lastMaxUSN, lastSyncAt string
database.MustScan(t, "scanning last upgrade",
db.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemLastUpgrade), &lastUpgrade)
database.MustScan(t, "scanning last max usn",
db.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemLastMaxUSN), &lastMaxUSN)
database.MustScan(t, "scanning last sync at",
db.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemLastSyncAt), &lastSyncAt)
assert.NotEqual(t, lastUpgrade, "", "last upgrade should not be empty")
assert.NotEqual(t, lastMaxUSN, "", "last max usn should not be empty")
assert.NotEqual(t, lastSyncAt, "", "last sync at should not be empty")
}
func TestAddNote_NewBook_BodyFlag(t *testing.T) {
// Set up
testutils.RunDnoteCmd(t, opts, binaryName, "add", "js", "-c", "foo")
defer testutils.RemoveDir(t, opts.HomeDir)
db := database.OpenTestDB(t, opts.DnoteDir)
// Execute
// Test
var noteCount, bookCount int
database.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
database.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
assert.Equalf(t, bookCount, 1, "book count mismatch")
assert.Equalf(t, noteCount, 1, "note count mismatch")
var book database.Book
database.MustScan(t, "getting book", db.QueryRow("SELECT uuid, dirty FROM books where label = ?", "js"), &book.UUID, &book.Dirty)
var note database.Note
database.MustScan(t, "getting note",
db.QueryRow("SELECT uuid, body, added_on, dirty FROM notes where book_uuid = ?", book.UUID), &note.UUID, &note.Body, &note.AddedOn, &note.Dirty)
assert.Equal(t, book.Dirty, true, "Book dirty mismatch")
assert.NotEqual(t, note.UUID, "", "Note should have UUID")
assert.Equal(t, note.Body, "foo", "Note body mismatch")
assert.Equal(t, note.Dirty, true, "Note dirty mismatch")
assert.NotEqual(t, note.AddedOn, int64(0), "Note added_on mismatch")
}
func TestAddNote_ExistingBook_BodyFlag(t *testing.T) {
// Setup
db := database.InitTestDB(t, fmt.Sprintf("%s/%s", opts.DnoteDir, consts.DnoteDBFileName), nil)
testutils.Setup3(t, db)
// Execute
testutils.RunDnoteCmd(t, opts, binaryName, "add", "js", "-c", "foo")
defer testutils.RemoveDir(t, opts.HomeDir)
// Test
var noteCount, bookCount int
database.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
database.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
assert.Equalf(t, bookCount, 1, "book count mismatch")
assert.Equalf(t, noteCount, 2, "note count mismatch")
var n1, n2 database.Note
database.MustScan(t, "getting n1",
db.QueryRow("SELECT uuid, body, added_on, dirty FROM notes WHERE book_uuid = ? AND uuid = ?", "js-book-uuid", "43827b9a-c2b0-4c06-a290-97991c896653"), &n1.UUID, &n1.Body, &n1.AddedOn, &n1.Dirty)
database.MustScan(t, "getting n2",
db.QueryRow("SELECT uuid, body, added_on, dirty FROM notes WHERE book_uuid = ? AND body = ?", "js-book-uuid", "foo"), &n2.UUID, &n2.Body, &n2.AddedOn, &n2.Dirty)
var book database.Book
database.MustScan(t, "getting book", db.QueryRow("SELECT dirty FROM books where label = ?", "js"), &book.Dirty)
assert.Equal(t, book.Dirty, false, "Book dirty mismatch")
assert.NotEqual(t, n1.UUID, "", "n1 should have UUID")
assert.Equal(t, n1.Body, "Booleans have toString()", "n1 body mismatch")
assert.Equal(t, n1.AddedOn, int64(1515199943), "n1 added_on mismatch")
assert.Equal(t, n1.Dirty, false, "n1 dirty mismatch")
assert.NotEqual(t, n2.UUID, "", "n2 should have UUID")
assert.Equal(t, n2.Body, "foo", "n2 body mismatch")
assert.Equal(t, n2.Dirty, true, "n2 dirty mismatch")
}
func TestEditNote_BodyFlag(t *testing.T) {
// Setup
db := database.InitTestDB(t, fmt.Sprintf("%s/%s", opts.DnoteDir, consts.DnoteDBFileName), nil)
testutils.Setup4(t, db)
// Execute
testutils.RunDnoteCmd(t, opts, binaryName, "edit", "2", "-c", "foo bar")
defer testutils.RemoveDir(t, opts.HomeDir)
// Test
var noteCount, bookCount int
database.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
database.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
assert.Equalf(t, bookCount, 1, "book count mismatch")
assert.Equalf(t, noteCount, 2, "note count mismatch")
var n1, n2 database.Note
database.MustScan(t, "getting n1",
db.QueryRow("SELECT uuid, body, added_on, dirty FROM notes where book_uuid = ? AND uuid = ?", "js-book-uuid", "43827b9a-c2b0-4c06-a290-97991c896653"), &n1.UUID, &n1.Body, &n1.AddedOn, &n1.Dirty)
database.MustScan(t, "getting n2",
db.QueryRow("SELECT uuid, body, added_on, dirty FROM notes where book_uuid = ? AND uuid = ?", "js-book-uuid", "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f"), &n2.UUID, &n2.Body, &n2.AddedOn, &n2.Dirty)
assert.Equal(t, n1.UUID, "43827b9a-c2b0-4c06-a290-97991c896653", "n1 should have UUID")
assert.Equal(t, n1.Body, "Booleans have toString()", "n1 body mismatch")
assert.Equal(t, n1.Dirty, false, "n1 dirty mismatch")
assert.Equal(t, n2.UUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "Note should have UUID")
assert.Equal(t, n2.Body, "foo bar", "Note body mismatch")
assert.Equal(t, n2.Dirty, true, "n2 dirty mismatch")
assert.NotEqual(t, n2.EditedOn, 0, "Note edited_on mismatch")
}
func TestRemoveNote(t *testing.T) {
// Setup
db := database.InitTestDB(t, fmt.Sprintf("%s/%s", opts.DnoteDir, consts.DnoteDBFileName), nil)
testutils.Setup2(t, db)
// Execute
testutils.WaitDnoteCmd(t, opts, testutils.UserConfirm, binaryName, "remove", "js", "1")
defer testutils.RemoveDir(t, opts.HomeDir)
// Test
var noteCount, bookCount, jsNoteCount, linuxNoteCount int
database.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
database.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
database.MustScan(t, "counting js notes", db.QueryRow("SELECT count(*) FROM notes WHERE book_uuid = ?", "js-book-uuid"), &jsNoteCount)
database.MustScan(t, "counting linux notes", db.QueryRow("SELECT count(*) FROM notes WHERE book_uuid = ?", "linux-book-uuid"), &linuxNoteCount)
assert.Equalf(t, bookCount, 2, "book count mismatch")
assert.Equalf(t, noteCount, 3, "note count mismatch")
assert.Equal(t, jsNoteCount, 2, "js book should have 2 notes")
assert.Equal(t, linuxNoteCount, 1, "linux book book should have 1 note")
var b1, b2 database.Book
var n1, n2, n3 database.Note
database.MustScan(t, "getting b1",
db.QueryRow("SELECT label, deleted, usn FROM books WHERE uuid = ?", "js-book-uuid"),
&b1.Label, &b1.Deleted, &b1.USN)
database.MustScan(t, "getting b2",
db.QueryRow("SELECT label, deleted, usn FROM books WHERE uuid = ?", "linux-book-uuid"),
&b2.Label, &b2.Deleted, &b2.USN)
database.MustScan(t, "getting n1",
db.QueryRow("SELECT uuid, body, added_on, deleted, dirty, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "js-book-uuid", "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f"),
&n1.UUID, &n1.Body, &n1.AddedOn, &n1.Deleted, &n1.Dirty, &n1.USN)
database.MustScan(t, "getting n2",
db.QueryRow("SELECT uuid, body, added_on, deleted, dirty, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "js-book-uuid", "43827b9a-c2b0-4c06-a290-97991c896653"),
&n2.UUID, &n2.Body, &n2.AddedOn, &n2.Deleted, &n2.Dirty, &n2.USN)
database.MustScan(t, "getting n3",
db.QueryRow("SELECT uuid, body, added_on, deleted, dirty, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "linux-book-uuid", "3e065d55-6d47-42f2-a6bf-f5844130b2d2"),
&n3.UUID, &n3.Body, &n3.AddedOn, &n3.Deleted, &n3.Dirty, &n3.USN)
assert.Equal(t, b1.Label, "js", "b1 label mismatch")
assert.Equal(t, b1.Deleted, false, "b1 deleted mismatch")
assert.Equal(t, b1.Dirty, false, "b1 Dirty mismatch")
assert.Equal(t, b1.USN, 111, "b1 usn mismatch")
assert.Equal(t, b2.Label, "linux", "b2 label mismatch")
assert.Equal(t, b2.Deleted, false, "b2 deleted mismatch")
assert.Equal(t, b2.Dirty, false, "b2 Dirty mismatch")
assert.Equal(t, b2.USN, 122, "b2 usn mismatch")
assert.Equal(t, n1.UUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "n1 should have UUID")
assert.Equal(t, n1.Body, "", "n1 body mismatch")
assert.Equal(t, n1.Deleted, true, "n1 deleted mismatch")
assert.Equal(t, n1.Dirty, true, "n1 Dirty mismatch")
assert.Equal(t, n1.USN, 11, "n1 usn mismatch")
assert.Equal(t, n2.UUID, "43827b9a-c2b0-4c06-a290-97991c896653", "n2 should have UUID")
assert.Equal(t, n2.Body, "n2 body", "n2 body mismatch")
assert.Equal(t, n2.Deleted, false, "n2 deleted mismatch")
assert.Equal(t, n2.Dirty, false, "n2 Dirty mismatch")
assert.Equal(t, n2.USN, 12, "n2 usn mismatch")
assert.Equal(t, n3.UUID, "3e065d55-6d47-42f2-a6bf-f5844130b2d2", "n3 should have UUID")
assert.Equal(t, n3.Body, "n3 body", "n3 body mismatch")
assert.Equal(t, n3.Deleted, false, "n3 deleted mismatch")
assert.Equal(t, n3.Dirty, false, "n3 Dirty mismatch")
assert.Equal(t, n3.USN, 13, "n3 usn mismatch")
}
func TestRemoveBook(t *testing.T) {
// Setup
db := database.InitTestDB(t, fmt.Sprintf("%s/%s", opts.DnoteDir, consts.DnoteDBFileName), nil)
testutils.Setup2(t, db)
// Execute
testutils.WaitDnoteCmd(t, opts, testutils.UserConfirm, binaryName, "remove", "-b", "js")
defer testutils.RemoveDir(t, opts.HomeDir)
// Test
var noteCount, bookCount, jsNoteCount, linuxNoteCount int
database.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount)
database.MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), &noteCount)
database.MustScan(t, "counting js notes", db.QueryRow("SELECT count(*) FROM notes WHERE book_uuid = ?", "js-book-uuid"), &jsNoteCount)
database.MustScan(t, "counting linux notes", db.QueryRow("SELECT count(*) FROM notes WHERE book_uuid = ?", "linux-book-uuid"), &linuxNoteCount)
assert.Equalf(t, bookCount, 2, "book count mismatch")
assert.Equalf(t, noteCount, 3, "note count mismatch")
assert.Equal(t, jsNoteCount, 2, "js book should have 2 notes")
assert.Equal(t, linuxNoteCount, 1, "linux book book should have 1 note")
var b1, b2 database.Book
var n1, n2, n3 database.Note
database.MustScan(t, "getting b1",
db.QueryRow("SELECT label, dirty, deleted, usn FROM books WHERE uuid = ?", "js-book-uuid"),
&b1.Label, &b1.Dirty, &b1.Deleted, &b1.USN)
database.MustScan(t, "getting b2",
db.QueryRow("SELECT label, dirty, deleted, usn FROM books WHERE uuid = ?", "linux-book-uuid"),
&b2.Label, &b2.Dirty, &b2.Deleted, &b2.USN)
database.MustScan(t, "getting n1",
db.QueryRow("SELECT uuid, body, added_on, dirty, deleted, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "js-book-uuid", "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f"),
&n1.UUID, &n1.Body, &n1.AddedOn, &n1.Deleted, &n1.Dirty, &n1.USN)
database.MustScan(t, "getting n2",
db.QueryRow("SELECT uuid, body, added_on, dirty, deleted, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "js-book-uuid", "43827b9a-c2b0-4c06-a290-97991c896653"),
&n2.UUID, &n2.Body, &n2.AddedOn, &n2.Deleted, &n2.Dirty, &n2.USN)
database.MustScan(t, "getting n3",
db.QueryRow("SELECT uuid, body, added_on, dirty, deleted, usn FROM notes WHERE book_uuid = ? AND uuid = ?", "linux-book-uuid", "3e065d55-6d47-42f2-a6bf-f5844130b2d2"),
&n3.UUID, &n3.Body, &n3.AddedOn, &n3.Deleted, &n3.Dirty, &n3.USN)
assert.NotEqual(t, b1.Label, "js", "b1 label mismatch")
assert.Equal(t, b1.Dirty, true, "b1 Dirty mismatch")
assert.Equal(t, b1.Deleted, true, "b1 deleted mismatch")
assert.Equal(t, b1.USN, 111, "b1 usn mismatch")
assert.Equal(t, b2.Label, "linux", "b2 label mismatch")
assert.Equal(t, b2.Dirty, false, "b2 Dirty mismatch")
assert.Equal(t, b2.Deleted, false, "b2 deleted mismatch")
assert.Equal(t, b2.USN, 122, "b2 usn mismatch")
assert.Equal(t, n1.UUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "n1 should have UUID")
assert.Equal(t, n1.Body, "", "n1 body mismatch")
assert.Equal(t, n1.Dirty, true, "n1 Dirty mismatch")
assert.Equal(t, n1.Deleted, true, "n1 deleted mismatch")
assert.Equal(t, n1.USN, 11, "n1 usn mismatch")
assert.Equal(t, n2.UUID, "43827b9a-c2b0-4c06-a290-97991c896653", "n2 should have UUID")
assert.Equal(t, n2.Body, "", "n2 body mismatch")
assert.Equal(t, n2.Dirty, true, "n2 Dirty mismatch")
assert.Equal(t, n2.Deleted, true, "n2 deleted mismatch")
assert.Equal(t, n2.USN, 12, "n2 usn mismatch")
assert.Equal(t, n3.UUID, "3e065d55-6d47-42f2-a6bf-f5844130b2d2", "n3 should have UUID")
assert.Equal(t, n3.Body, "n3 body", "n3 body mismatch")
assert.Equal(t, n3.Dirty, false, "n3 Dirty mismatch")
assert.Equal(t, n3.Deleted, false, "n3 deleted mismatch")
assert.Equal(t, n3.USN, 13, "n3 usn mismatch")
}

View file

@ -27,9 +27,9 @@ import (
"os"
"time"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/cli/utils"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/dnote/dnote/pkg/cli/utils"
"github.com/pkg/errors"
"github.com/satori/go.uuid"
"gopkg.in/yaml.v2"
@ -82,7 +82,7 @@ func makeSchema(complete bool) schema {
}
// Legacy performs migration on JSON-based dnote if necessary
func Legacy(ctx infra.DnoteCtx) error {
func Legacy(ctx context.DnoteCtx) error {
// If schema does not exist, no need run a legacy migration
schemaPath := getSchemaPath(ctx)
if ok := utils.FileExists(schemaPath); !ok {
@ -106,7 +106,7 @@ func Legacy(ctx infra.DnoteCtx) error {
// performMigration backs up current .dnote data, performs migration, and
// restores or cleans backups depending on if there is an error
func performMigration(ctx infra.DnoteCtx, migrationID int) error {
func performMigration(ctx context.DnoteCtx, migrationID int) error {
// legacyMigrationV8 is the final migration of the legacy JSON Dnote migration
// migrate to sqlite and return
if migrationID == legacyMigrationV8 {
@ -162,7 +162,7 @@ func performMigration(ctx infra.DnoteCtx, migrationID int) error {
}
// backupDnoteDir backs up the dnote directory to a temporary backup directory
func backupDnoteDir(ctx infra.DnoteCtx) error {
func backupDnoteDir(ctx context.DnoteCtx) error {
srcPath := fmt.Sprintf("%s/.dnote", ctx.HomeDir)
tmpPath := fmt.Sprintf("%s/%s", ctx.HomeDir, backupDirName)
@ -173,14 +173,14 @@ func backupDnoteDir(ctx infra.DnoteCtx) error {
return nil
}
func restoreBackup(ctx infra.DnoteCtx) error {
func restoreBackup(ctx context.DnoteCtx) error {
var err error
defer func() {
if err != nil {
log.Printf(`Failed to restore backup for a failed migration.
Don't worry. Your data is still intact in the backup directory.
Get help on https://github.com/dnote/dnote/cli/issues`)
Get help on https://github.com/dnote/dnote/pkg/cli/issues`)
}
}()
@ -198,7 +198,7 @@ func restoreBackup(ctx infra.DnoteCtx) error {
return nil
}
func clearBackup(ctx infra.DnoteCtx) error {
func clearBackup(ctx context.DnoteCtx) error {
backupPath := fmt.Sprintf("%s/%s", ctx.HomeDir, backupDirName)
if err := os.RemoveAll(backupPath); err != nil {
@ -209,11 +209,11 @@ func clearBackup(ctx infra.DnoteCtx) error {
}
// getSchemaPath returns the path to the file containing schema info
func getSchemaPath(ctx infra.DnoteCtx) string {
func getSchemaPath(ctx context.DnoteCtx) string {
return fmt.Sprintf("%s/%s", ctx.DnoteDir, schemaFilename)
}
func readSchema(ctx infra.DnoteCtx) (schema, error) {
func readSchema(ctx context.DnoteCtx) (schema, error) {
var ret schema
path := getSchemaPath(ctx)
@ -231,7 +231,7 @@ func readSchema(ctx infra.DnoteCtx) (schema, error) {
return ret, nil
}
func writeSchema(ctx infra.DnoteCtx, s schema) error {
func writeSchema(ctx context.DnoteCtx, s schema) error {
path := getSchemaPath(ctx)
d, err := yaml.Marshal(&s)
if err != nil {
@ -245,7 +245,7 @@ func writeSchema(ctx infra.DnoteCtx, s schema) error {
return nil
}
func getUnrunMigrations(ctx infra.DnoteCtx) ([]int, error) {
func getUnrunMigrations(ctx context.DnoteCtx) ([]int, error) {
var ret []int
schema, err := readSchema(ctx)
@ -265,7 +265,7 @@ func getUnrunMigrations(ctx infra.DnoteCtx) ([]int, error) {
return ret, nil
}
func updateSchemaVersion(ctx infra.DnoteCtx, mID int) error {
func updateSchemaVersion(ctx context.DnoteCtx, mID int) error {
s, err := readSchema(ctx)
if err != nil {
return errors.Wrap(err, "Failed to read schema")
@ -470,7 +470,7 @@ var migrateToV8SystemKeyBookMark = "bookmark"
/***** migrations **/
// migrateToV1 deletes YAML archive if exists
func migrateToV1(ctx infra.DnoteCtx) error {
func migrateToV1(ctx context.DnoteCtx) error {
yamlPath := fmt.Sprintf("%s/%s", ctx.HomeDir, ".dnote-yaml-archived")
if !utils.FileExists(yamlPath) {
return nil
@ -483,7 +483,7 @@ func migrateToV1(ctx infra.DnoteCtx) error {
return nil
}
func migrateToV2(ctx infra.DnoteCtx) error {
func migrateToV2(ctx context.DnoteCtx) error {
notePath := fmt.Sprintf("%s/dnote", ctx.DnoteDir)
b, err := ioutil.ReadFile(notePath)
@ -534,7 +534,7 @@ func migrateToV2(ctx infra.DnoteCtx) error {
}
// migrateToV3 generates actions for existing dnote
func migrateToV3(ctx infra.DnoteCtx) error {
func migrateToV3(ctx context.DnoteCtx) error {
notePath := fmt.Sprintf("%s/dnote", ctx.DnoteDir)
actionsPath := fmt.Sprintf("%s/actions", ctx.DnoteDir)
@ -621,7 +621,7 @@ func getEditorCommand() string {
}
}
func migrateToV4(ctx infra.DnoteCtx) error {
func migrateToV4(ctx context.DnoteCtx) error {
configPath := fmt.Sprintf("%s/dnoterc", ctx.DnoteDir)
b, err := ioutil.ReadFile(configPath)
@ -654,7 +654,7 @@ func migrateToV4(ctx infra.DnoteCtx) error {
}
// migrateToV5 migrates actions
func migrateToV5(ctx infra.DnoteCtx) error {
func migrateToV5(ctx context.DnoteCtx) error {
actionsPath := fmt.Sprintf("%s/actions", ctx.DnoteDir)
b, err := ioutil.ReadFile(actionsPath)
@ -719,7 +719,7 @@ func migrateToV5(ctx infra.DnoteCtx) error {
}
// migrateToV6 adds a 'public' field to notes
func migrateToV6(ctx infra.DnoteCtx) error {
func migrateToV6(ctx context.DnoteCtx) error {
notePath := fmt.Sprintf("%s/dnote", ctx.DnoteDir)
b, err := ioutil.ReadFile(notePath)
@ -773,8 +773,8 @@ func migrateToV6(ctx infra.DnoteCtx) error {
// migrateToV7 migrates data of edit_note action to the proper version which is
// EditNoteDataV2. Due to a bug, edit logged actions with schema version '2'
// but with a data of EditNoteDataV1. https://github.com/dnote/dnote/cli/issues/107
func migrateToV7(ctx infra.DnoteCtx) error {
// but with a data of EditNoteDataV1. https://github.com/dnote/dnote/pkg/cli/issues/107
func migrateToV7(ctx context.DnoteCtx) error {
actionPath := fmt.Sprintf("%s/actions", ctx.DnoteDir)
b, err := ioutil.ReadFile(actionPath)
@ -838,7 +838,7 @@ func migrateToV7(ctx infra.DnoteCtx) error {
}
// migrateToV8 migrates dnote data to sqlite database
func migrateToV8(ctx infra.DnoteCtx) error {
func migrateToV8(ctx context.DnoteCtx) error {
tx, err := ctx.DB.Begin()
if err != nil {
return errors.Wrap(err, "beginning a transaction")

View file

@ -27,18 +27,38 @@ import (
"reflect"
"testing"
"github.com/dnote/dnote/cli/testutils"
"github.com/dnote/dnote/cli/utils"
"github.com/dnote/dnote/pkg/assert"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/testutils"
"github.com/dnote/dnote/pkg/cli/utils"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
)
func TestMigrateToV1(t *testing.T) {
func setupEnv(t *testing.T, homeDir string) context.DnoteCtx {
dnoteDir := fmt.Sprintf("%s/.dnote", homeDir)
if err := os.MkdirAll(dnoteDir, 0755); err != nil {
t.Fatal(errors.Wrap(err, "preparing dnote dir"))
}
return context.DnoteCtx{
HomeDir: homeDir,
DnoteDir: dnoteDir,
}
}
func teardownEnv(t *testing.T, ctx context.DnoteCtx) {
if err := os.RemoveAll(ctx.DnoteDir); err != nil {
t.Fatal(errors.Wrap(err, "tearing down the dnote dir"))
}
}
func TestMigrateToV1(t *testing.T) {
t.Run("yaml exists", func(t *testing.T) {
// set up
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
ctx := setupEnv(t, "../tmp")
defer teardownEnv(t, ctx)
yamlPath, err := filepath.Abs(filepath.Join(ctx.HomeDir, ".dnote-yaml-archived"))
if err != nil {
@ -59,8 +79,8 @@ func TestMigrateToV1(t *testing.T) {
t.Run("yaml does not exist", func(t *testing.T) {
// set up
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
ctx := setupEnv(t, "../tmp")
defer teardownEnv(t, ctx)
yamlPath, err := filepath.Abs(filepath.Join(ctx.HomeDir, ".dnote-yaml-archived"))
if err != nil {
@ -80,10 +100,10 @@ func TestMigrateToV1(t *testing.T) {
}
func TestMigrateToV2(t *testing.T) {
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
ctx := setupEnv(t, "../tmp")
defer teardownEnv(t, ctx)
testutils.CopyFixture(ctx, "./fixtures/legacy-2-pre-dnote.json", "dnote")
testutils.CopyFixture(t, ctx, "./fixtures/legacy-2-pre-dnote.json", "dnote")
// execute
if err := migrateToV2(ctx); err != nil {
@ -99,25 +119,25 @@ func TestMigrateToV2(t *testing.T) {
}
for _, book := range postDnote {
testutils.AssertNotEqual(t, book.Name, "", "Book name was not populated")
assert.NotEqual(t, book.Name, "", "Book name was not populated")
for _, note := range book.Notes {
if len(note.UUID) == 8 {
t.Errorf("Note UUID was not migrated. It has length of %d", len(note.UUID))
}
testutils.AssertNotEqual(t, note.AddedOn, int64(0), "AddedOn was not carried over")
testutils.AssertEqual(t, note.EditedOn, int64(0), "EditedOn was not created properly")
assert.NotEqual(t, note.AddedOn, int64(0), "AddedOn was not carried over")
assert.Equal(t, note.EditedOn, int64(0), "EditedOn was not created properly")
}
}
}
func TestMigrateToV3(t *testing.T) {
// set up
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
ctx := setupEnv(t, "../tmp")
defer teardownEnv(t, ctx)
testutils.CopyFixture(ctx, "./fixtures/legacy-3-pre-dnote.json", "dnote")
testutils.CopyFixture(t, ctx, "./fixtures/legacy-3-pre-dnote.json", "dnote")
// execute
if err := migrateToV3(ctx); err != nil {
@ -137,22 +157,22 @@ func TestMigrateToV3(t *testing.T) {
t.Fatal(errors.Wrap(err, "Failed to unmarshal the actions").Error())
}
testutils.AssertEqual(t, len(actions), 6, "actions length mismatch")
assert.Equal(t, len(actions), 6, "actions length mismatch")
for _, book := range postDnote {
for _, note := range book.Notes {
testutils.AssertNotEqual(t, note.AddedOn, int64(0), "AddedOn was not carried over")
assert.NotEqual(t, note.AddedOn, int64(0), "AddedOn was not carried over")
}
}
}
func TestMigrateToV4(t *testing.T) {
// set up
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
ctx := setupEnv(t, "../tmp")
defer teardownEnv(t, ctx)
defer os.Setenv("EDITOR", "")
testutils.CopyFixture(ctx, "./fixtures/legacy-4-pre-dnoterc.yaml", "dnoterc")
testutils.CopyFixture(t, ctx, "./fixtures/legacy-4-pre-dnoterc.yaml", "dnoterc")
// execute
os.Setenv("EDITOR", "vim")
@ -167,16 +187,16 @@ func TestMigrateToV4(t *testing.T) {
t.Fatal(errors.Wrap(err, "Failed to unmarshal the result into Dnote").Error())
}
testutils.AssertEqual(t, config.APIKey, "Oev6e1082ORasdf9rjkfjkasdfjhgei", "api key mismatch")
testutils.AssertEqual(t, config.Editor, "vim", "editor mismatch")
assert.Equal(t, config.APIKey, "Oev6e1082ORasdf9rjkfjkasdfjhgei", "api key mismatch")
assert.Equal(t, config.Editor, "vim", "editor mismatch")
}
func TestMigrateToV5(t *testing.T) {
// set up
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
ctx := setupEnv(t, "../tmp")
defer teardownEnv(t, ctx)
testutils.CopyFixture(ctx, "./fixtures/legacy-5-pre-actions.json", "actions")
testutils.CopyFixture(t, ctx, "./fixtures/legacy-5-pre-actions.json", "actions")
// execute
if err := migrateToV5(ctx); err != nil {
@ -201,10 +221,10 @@ func TestMigrateToV5(t *testing.T) {
migrated := migratedActions[idx]
old := oldActions[idx]
testutils.AssertNotEqual(t, migrated.UUID, "", fmt.Sprintf("uuid mismatch for migrated item with index %d", idx))
testutils.AssertEqual(t, migrated.Schema, 1, fmt.Sprintf("schema mismatch for migrated item with index %d", idx))
testutils.AssertEqual(t, migrated.Timestamp, old.Timestamp, fmt.Sprintf("timestamp mismatch for migrated item with index %d", idx))
testutils.AssertEqual(t, migrated.Type, old.Type, fmt.Sprintf("timestamp mismatch for migrated item with index %d", idx))
assert.NotEqual(t, migrated.UUID, "", fmt.Sprintf("uuid mismatch for migrated item with index %d", idx))
assert.Equal(t, migrated.Schema, 1, fmt.Sprintf("schema mismatch for migrated item with index %d", idx))
assert.Equal(t, migrated.Timestamp, old.Timestamp, fmt.Sprintf("timestamp mismatch for migrated item with index %d", idx))
assert.Equal(t, migrated.Type, old.Type, fmt.Sprintf("timestamp mismatch for migrated item with index %d", idx))
switch migrated.Type {
case migrateToV5ActionAddNote:
@ -216,9 +236,9 @@ func TestMigrateToV5(t *testing.T) {
t.Fatal(errors.Wrap(err, "unmarshalling new data").Error())
}
testutils.AssertEqual(t, oldData.BookName, migratedData.BookName, fmt.Sprintf("data book_name mismatch for item idx %d", idx))
testutils.AssertEqual(t, oldData.Content, migratedData.Content, fmt.Sprintf("data content mismatch for item idx %d", idx))
testutils.AssertEqual(t, oldData.NoteUUID, migratedData.NoteUUID, fmt.Sprintf("data note_uuid mismatch for item idx %d", idx))
assert.Equal(t, oldData.BookName, migratedData.BookName, fmt.Sprintf("data book_name mismatch for item idx %d", idx))
assert.Equal(t, oldData.Content, migratedData.Content, fmt.Sprintf("data content mismatch for item idx %d", idx))
assert.Equal(t, oldData.NoteUUID, migratedData.NoteUUID, fmt.Sprintf("data note_uuid mismatch for item idx %d", idx))
case migrateToV5ActionRemoveNote:
var oldData, migratedData migrateToV5RemoveNoteData
if err := json.Unmarshal(old.Data, &oldData); err != nil {
@ -228,8 +248,8 @@ func TestMigrateToV5(t *testing.T) {
t.Fatal(errors.Wrap(err, "unmarshalling new data").Error())
}
testutils.AssertEqual(t, oldData.BookName, migratedData.BookName, fmt.Sprintf("data book_name mismatch for item idx %d", idx))
testutils.AssertEqual(t, oldData.NoteUUID, migratedData.NoteUUID, fmt.Sprintf("data note_uuid mismatch for item idx %d", idx))
assert.Equal(t, oldData.BookName, migratedData.BookName, fmt.Sprintf("data book_name mismatch for item idx %d", idx))
assert.Equal(t, oldData.NoteUUID, migratedData.NoteUUID, fmt.Sprintf("data note_uuid mismatch for item idx %d", idx))
case migrateToV5ActionAddBook:
var oldData, migratedData migrateToV5AddBookData
if err := json.Unmarshal(old.Data, &oldData); err != nil {
@ -239,7 +259,7 @@ func TestMigrateToV5(t *testing.T) {
t.Fatal(errors.Wrap(err, "unmarshalling new data").Error())
}
testutils.AssertEqual(t, oldData.BookName, migratedData.BookName, fmt.Sprintf("data book_name mismatch for item idx %d", idx))
assert.Equal(t, oldData.BookName, migratedData.BookName, fmt.Sprintf("data book_name mismatch for item idx %d", idx))
case migrateToV5ActionRemoveBook:
var oldData, migratedData migrateToV5RemoveBookData
if err := json.Unmarshal(old.Data, &oldData); err != nil {
@ -249,7 +269,7 @@ func TestMigrateToV5(t *testing.T) {
t.Fatal(errors.Wrap(err, "unmarshalling new data").Error())
}
testutils.AssertEqual(t, oldData.BookName, migratedData.BookName, fmt.Sprintf("data book_name mismatch for item idx %d", idx))
assert.Equal(t, oldData.BookName, migratedData.BookName, fmt.Sprintf("data book_name mismatch for item idx %d", idx))
case migrateToV5ActionEditNote:
var oldData migrateToV5PreEditNoteData
var migratedData migrateToV5PostEditNoteData
@ -260,20 +280,20 @@ func TestMigrateToV5(t *testing.T) {
t.Fatal(errors.Wrap(err, "unmarshalling new data").Error())
}
testutils.AssertEqual(t, oldData.NoteUUID, migratedData.NoteUUID, fmt.Sprintf("data note_uuid mismatch for item idx %d", idx))
testutils.AssertEqual(t, oldData.Content, migratedData.Content, fmt.Sprintf("data content mismatch for item idx %d", idx))
testutils.AssertEqual(t, oldData.BookName, migratedData.FromBook, "book_name should have been renamed to from_book")
testutils.AssertEqual(t, migratedData.ToBook, "", "to_book should be empty")
assert.Equal(t, oldData.NoteUUID, migratedData.NoteUUID, fmt.Sprintf("data note_uuid mismatch for item idx %d", idx))
assert.Equal(t, oldData.Content, migratedData.Content, fmt.Sprintf("data content mismatch for item idx %d", idx))
assert.Equal(t, oldData.BookName, migratedData.FromBook, "book_name should have been renamed to from_book")
assert.Equal(t, migratedData.ToBook, "", "to_book should be empty")
}
}
}
func TestMigrateToV6(t *testing.T) {
// set up
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
ctx := setupEnv(t, "../tmp")
defer teardownEnv(t, ctx)
testutils.CopyFixture(ctx, "./fixtures/legacy-6-pre-dnote.json", "dnote")
testutils.CopyFixture(t, ctx, "./fixtures/legacy-6-pre-dnote.json", "dnote")
// execute
if err := migrateToV6(ctx); err != nil {
@ -287,7 +307,7 @@ func TestMigrateToV6(t *testing.T) {
t.Fatal(errors.Wrap(err, "Failed to unmarshal the result into Dnote").Error())
}
b = testutils.ReadFileAbs("./fixtures/legacy-6-post-dnote.json")
b = utils.ReadFileAbs("./fixtures/legacy-6-post-dnote.json")
var expected migrateToV6PostDnote
if err := json.Unmarshal(b, &expected); err != nil {
t.Fatal(errors.Wrap(err, "Failed to unmarshal the result into Dnote").Error())
@ -300,10 +320,10 @@ func TestMigrateToV6(t *testing.T) {
func TestMigrateToV7(t *testing.T) {
// set up
ctx := testutils.InitEnv(t, "../tmp", "../testutils/fixtures/schema.sql", true)
defer testutils.TeardownEnv(ctx)
ctx := setupEnv(t, "../tmp")
defer teardownEnv(t, ctx)
testutils.CopyFixture(ctx, "./fixtures/legacy-7-pre-actions.json", "actions")
testutils.CopyFixture(t, ctx, "./fixtures/legacy-7-pre-actions.json", "actions")
// execute
if err := migrateToV7(ctx); err != nil {
@ -317,32 +337,28 @@ func TestMigrateToV7(t *testing.T) {
t.Fatal(errors.Wrap(err, "unmarshalling the result").Error())
}
b2 := testutils.ReadFileAbs("./fixtures/legacy-7-post-actions.json")
b2 := utils.ReadFileAbs("./fixtures/legacy-7-post-actions.json")
var expected []migrateToV7Action
if err := json.Unmarshal(b, &expected); err != nil {
t.Fatal(errors.Wrap(err, "unmarshalling the result into Dnote").Error())
}
ok, err := testutils.IsEqualJSON(b, b2)
if err != nil {
t.Fatal(errors.Wrap(err, "comparing JSON").Error())
}
if !ok {
t.Errorf("Result does not match.\nActual: %+v\nExpected: %+v", got, expected)
}
assert.EqualJSON(t, string(b), string(b2), "Result does not match")
}
func TestMigrateToV8(t *testing.T) {
ctx := testutils.InitEnv(t, "../tmp", "./fixtures/local-1-pre-schema.sql", false)
defer testutils.TeardownEnv(ctx)
opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-1-pre-schema.sql", SkipMigration: true}
db := database.InitTestDB(t, "../tmp/.dnote/dnote-test.db", &opts)
defer database.CloseTestDB(t, db)
ctx := context.DnoteCtx{HomeDir: "../tmp", DnoteDir: "../tmp/.dnote", DB: db}
// set up
testutils.CopyFixture(ctx, "./fixtures/legacy-8-actions.json", "actions")
testutils.CopyFixture(ctx, "./fixtures/legacy-8-dnote.json", "dnote")
testutils.CopyFixture(ctx, "./fixtures/legacy-8-dnoterc.yaml", "dnoterc")
testutils.CopyFixture(ctx, "./fixtures/legacy-8-schema.yaml", "schema")
testutils.CopyFixture(ctx, "./fixtures/legacy-8-timestamps.yaml", "timestamps")
testutils.CopyFixture(t, ctx, "./fixtures/legacy-8-actions.json", "actions")
testutils.CopyFixture(t, ctx, "./fixtures/legacy-8-dnote.json", "dnote")
testutils.CopyFixture(t, ctx, "./fixtures/legacy-8-dnoterc.yaml", "dnoterc")
testutils.CopyFixture(t, ctx, "./fixtures/legacy-8-schema.yaml", "schema")
testutils.CopyFixture(t, ctx, "./fixtures/legacy-8-timestamps.yaml", "timestamps")
// execute
if err := migrateToV8(ctx); err != nil {
@ -370,7 +386,6 @@ func TestMigrateToV8(t *testing.T) {
}
// 2. test if notes and books are migrated
db := ctx.DB
var bookCount, noteCount int
err := db.QueryRow("SELECT count(*) FROM books").Scan(&bookCount)
@ -381,8 +396,8 @@ func TestMigrateToV8(t *testing.T) {
if err != nil {
panic(errors.Wrap(err, "counting notes"))
}
testutils.AssertEqual(t, bookCount, 2, "book count mismatch")
testutils.AssertEqual(t, noteCount, 3, "note count mismatch")
assert.Equal(t, bookCount, 2, "book count mismatch")
assert.Equal(t, noteCount, 3, "note count mismatch")
type bookInfo struct {
label string
@ -421,34 +436,34 @@ func TestMigrateToV8(t *testing.T) {
panic(errors.Wrap(err, "finding note 3"))
}
testutils.AssertNotEqual(t, b1.uuid, "", "book 1 uuid should have been generated")
testutils.AssertEqual(t, b1.label, "js", "book 1 label mismatch")
testutils.AssertNotEqual(t, b2.uuid, "", "book 2 uuid should have been generated")
testutils.AssertEqual(t, b2.label, "css", "book 2 label mismatch")
assert.NotEqual(t, b1.uuid, "", "book 1 uuid should have been generated")
assert.Equal(t, b1.label, "js", "book 1 label mismatch")
assert.NotEqual(t, b2.uuid, "", "book 2 uuid should have been generated")
assert.Equal(t, b2.label, "css", "book 2 label mismatch")
testutils.AssertEqual(t, n1.uuid, "d69edb54-5b31-4cdd-a4a5-34f0a0bfa153", "note 1 uuid mismatch")
testutils.AssertNotEqual(t, n1.id, 0, "note 1 id should have been generated")
testutils.AssertEqual(t, n1.bookUUID, b2.uuid, "note 1 book_uuid mismatch")
testutils.AssertEqual(t, n1.content, "css test 1", "note 1 content mismatch")
testutils.AssertEqual(t, n1.addedOn, int64(1536977237), "note 1 added_on mismatch")
testutils.AssertEqual(t, n1.editedOn, int64(1536977253), "note 1 edited_on mismatch")
testutils.AssertEqual(t, n1.public, false, "note 1 public mismatch")
assert.Equal(t, n1.uuid, "d69edb54-5b31-4cdd-a4a5-34f0a0bfa153", "note 1 uuid mismatch")
assert.NotEqual(t, n1.id, 0, "note 1 id should have been generated")
assert.Equal(t, n1.bookUUID, b2.uuid, "note 1 book_uuid mismatch")
assert.Equal(t, n1.content, "css test 1", "note 1 content mismatch")
assert.Equal(t, n1.addedOn, int64(1536977237), "note 1 added_on mismatch")
assert.Equal(t, n1.editedOn, int64(1536977253), "note 1 edited_on mismatch")
assert.Equal(t, n1.public, false, "note 1 public mismatch")
testutils.AssertEqual(t, n2.uuid, "35cbcab1-6a2a-4cc8-97e0-e73bbbd54626", "note 2 uuid mismatch")
testutils.AssertNotEqual(t, n2.id, 0, "note 2 id should have been generated")
testutils.AssertEqual(t, n2.bookUUID, b1.uuid, "note 2 book_uuid mismatch")
testutils.AssertEqual(t, n2.content, "js test 1", "note 2 content mismatch")
testutils.AssertEqual(t, n2.addedOn, int64(1536977229), "note 2 added_on mismatch")
testutils.AssertEqual(t, n2.editedOn, int64(0), "note 2 edited_on mismatch")
testutils.AssertEqual(t, n2.public, false, "note 2 public mismatch")
assert.Equal(t, n2.uuid, "35cbcab1-6a2a-4cc8-97e0-e73bbbd54626", "note 2 uuid mismatch")
assert.NotEqual(t, n2.id, 0, "note 2 id should have been generated")
assert.Equal(t, n2.bookUUID, b1.uuid, "note 2 book_uuid mismatch")
assert.Equal(t, n2.content, "js test 1", "note 2 content mismatch")
assert.Equal(t, n2.addedOn, int64(1536977229), "note 2 added_on mismatch")
assert.Equal(t, n2.editedOn, int64(0), "note 2 edited_on mismatch")
assert.Equal(t, n2.public, false, "note 2 public mismatch")
testutils.AssertEqual(t, n3.uuid, "7c1fcfb2-de8b-4350-88f0-fb3cbaf6630a", "note 3 uuid mismatch")
testutils.AssertNotEqual(t, n3.id, 0, "note 3 id should have been generated")
testutils.AssertEqual(t, n3.bookUUID, b1.uuid, "note 3 book_uuid mismatch")
testutils.AssertEqual(t, n3.content, "js test 2", "note 3 content mismatch")
testutils.AssertEqual(t, n3.addedOn, int64(1536977230), "note 3 added_on mismatch")
testutils.AssertEqual(t, n3.editedOn, int64(0), "note 3 edited_on mismatch")
testutils.AssertEqual(t, n3.public, false, "note 3 public mismatch")
assert.Equal(t, n3.uuid, "7c1fcfb2-de8b-4350-88f0-fb3cbaf6630a", "note 3 uuid mismatch")
assert.NotEqual(t, n3.id, 0, "note 3 id should have been generated")
assert.Equal(t, n3.bookUUID, b1.uuid, "note 3 book_uuid mismatch")
assert.Equal(t, n3.content, "js test 2", "note 3 content mismatch")
assert.Equal(t, n3.addedOn, int64(1536977230), "note 3 added_on mismatch")
assert.Equal(t, n3.editedOn, int64(0), "note 3 edited_on mismatch")
assert.Equal(t, n3.public, false, "note 3 public mismatch")
// 3. test if actions are migrated
var actionCount int
@ -457,7 +472,7 @@ func TestMigrateToV8(t *testing.T) {
panic(errors.Wrap(err, "counting actions"))
}
testutils.AssertEqual(t, actionCount, 11, "action count mismatch")
assert.Equal(t, actionCount, 11, "action count mismatch")
type actionInfo struct {
uuid string
@ -513,71 +528,71 @@ func TestMigrateToV8(t *testing.T) {
panic(errors.Wrap(err, "finding a11"))
}
testutils.AssertEqual(t, a1.uuid, "6145c1b7-f286-4d9f-b0f6-00d274baefc6", "action 1 uuid mismatch")
testutils.AssertEqual(t, a1.schema, 1, "action 1 schema mismatch")
testutils.AssertEqual(t, a1.actionType, "add_book", "action 1 type mismatch")
testutils.AssertEqual(t, a1.data, `{"book_name":"js"}`, "action 1 data mismatch")
testutils.AssertEqual(t, a1.timestamp, 1536977229, "action 1 timestamp mismatch")
assert.Equal(t, a1.uuid, "6145c1b7-f286-4d9f-b0f6-00d274baefc6", "action 1 uuid mismatch")
assert.Equal(t, a1.schema, 1, "action 1 schema mismatch")
assert.Equal(t, a1.actionType, "add_book", "action 1 type mismatch")
assert.Equal(t, a1.data, `{"book_name":"js"}`, "action 1 data mismatch")
assert.Equal(t, a1.timestamp, 1536977229, "action 1 timestamp mismatch")
testutils.AssertEqual(t, a2.uuid, "c048a56b-179c-4f31-9995-81e9b32b7dd6", "action 2 uuid mismatch")
testutils.AssertEqual(t, a2.schema, 2, "action 2 schema mismatch")
testutils.AssertEqual(t, a2.actionType, "add_note", "action 2 type mismatch")
testutils.AssertEqual(t, a2.data, `{"note_uuid":"35cbcab1-6a2a-4cc8-97e0-e73bbbd54626","book_name":"js","content":"js test 1","public":false}`, "action 2 data mismatch")
testutils.AssertEqual(t, a2.timestamp, 1536977229, "action 2 timestamp mismatch")
assert.Equal(t, a2.uuid, "c048a56b-179c-4f31-9995-81e9b32b7dd6", "action 2 uuid mismatch")
assert.Equal(t, a2.schema, 2, "action 2 schema mismatch")
assert.Equal(t, a2.actionType, "add_note", "action 2 type mismatch")
assert.Equal(t, a2.data, `{"note_uuid":"35cbcab1-6a2a-4cc8-97e0-e73bbbd54626","book_name":"js","content":"js test 1","public":false}`, "action 2 data mismatch")
assert.Equal(t, a2.timestamp, 1536977229, "action 2 timestamp mismatch")
testutils.AssertEqual(t, a3.uuid, "f557ef48-c304-47dc-adfb-46b7306e701f", "action 3 uuid mismatch")
testutils.AssertEqual(t, a3.schema, 2, "action 3 schema mismatch")
testutils.AssertEqual(t, a3.actionType, "add_note", "action 3 type mismatch")
testutils.AssertEqual(t, a3.data, `{"note_uuid":"7c1fcfb2-de8b-4350-88f0-fb3cbaf6630a","book_name":"js","content":"js test 2","public":false}`, "action 3 data mismatch")
testutils.AssertEqual(t, a3.timestamp, 1536977230, "action 3 timestamp mismatch")
assert.Equal(t, a3.uuid, "f557ef48-c304-47dc-adfb-46b7306e701f", "action 3 uuid mismatch")
assert.Equal(t, a3.schema, 2, "action 3 schema mismatch")
assert.Equal(t, a3.actionType, "add_note", "action 3 type mismatch")
assert.Equal(t, a3.data, `{"note_uuid":"7c1fcfb2-de8b-4350-88f0-fb3cbaf6630a","book_name":"js","content":"js test 2","public":false}`, "action 3 data mismatch")
assert.Equal(t, a3.timestamp, 1536977230, "action 3 timestamp mismatch")
testutils.AssertEqual(t, a4.uuid, "8d79db34-343d-4331-ae5b-24743f17ca7f", "action 4 uuid mismatch")
testutils.AssertEqual(t, a4.schema, 2, "action 4 schema mismatch")
testutils.AssertEqual(t, a4.actionType, "add_note", "action 4 type mismatch")
testutils.AssertEqual(t, a4.data, `{"note_uuid":"b23a88ba-b291-4294-9795-86b394db5dcf","book_name":"js","content":"js test 3","public":false}`, "action 4 data mismatch")
testutils.AssertEqual(t, a4.timestamp, 1536977234, "action 4 timestamp mismatch")
assert.Equal(t, a4.uuid, "8d79db34-343d-4331-ae5b-24743f17ca7f", "action 4 uuid mismatch")
assert.Equal(t, a4.schema, 2, "action 4 schema mismatch")
assert.Equal(t, a4.actionType, "add_note", "action 4 type mismatch")
assert.Equal(t, a4.data, `{"note_uuid":"b23a88ba-b291-4294-9795-86b394db5dcf","book_name":"js","content":"js test 3","public":false}`, "action 4 data mismatch")
assert.Equal(t, a4.timestamp, 1536977234, "action 4 timestamp mismatch")
testutils.AssertEqual(t, a5.uuid, "b9c1ed4a-e6b3-41f2-983b-593ec7b8b7a1", "action 5 uuid mismatch")
testutils.AssertEqual(t, a5.schema, 1, "action 5 schema mismatch")
testutils.AssertEqual(t, a5.actionType, "add_book", "action 5 type mismatch")
testutils.AssertEqual(t, a5.data, `{"book_name":"css"}`, "action 5 data mismatch")
testutils.AssertEqual(t, a5.timestamp, 1536977237, "action 5 timestamp mismatch")
assert.Equal(t, a5.uuid, "b9c1ed4a-e6b3-41f2-983b-593ec7b8b7a1", "action 5 uuid mismatch")
assert.Equal(t, a5.schema, 1, "action 5 schema mismatch")
assert.Equal(t, a5.actionType, "add_book", "action 5 type mismatch")
assert.Equal(t, a5.data, `{"book_name":"css"}`, "action 5 data mismatch")
assert.Equal(t, a5.timestamp, 1536977237, "action 5 timestamp mismatch")
testutils.AssertEqual(t, a6.uuid, "06ed7ef0-f171-4bd7-ae8e-97b5d06a4c49", "action 6 uuid mismatch")
testutils.AssertEqual(t, a6.schema, 2, "action 6 schema mismatch")
testutils.AssertEqual(t, a6.actionType, "add_note", "action 6 type mismatch")
testutils.AssertEqual(t, a6.data, `{"note_uuid":"d69edb54-5b31-4cdd-a4a5-34f0a0bfa153","book_name":"css","content":"js test 3","public":false}`, "action 6 data mismatch")
testutils.AssertEqual(t, a6.timestamp, 1536977237, "action 6 timestamp mismatch")
assert.Equal(t, a6.uuid, "06ed7ef0-f171-4bd7-ae8e-97b5d06a4c49", "action 6 uuid mismatch")
assert.Equal(t, a6.schema, 2, "action 6 schema mismatch")
assert.Equal(t, a6.actionType, "add_note", "action 6 type mismatch")
assert.Equal(t, a6.data, `{"note_uuid":"d69edb54-5b31-4cdd-a4a5-34f0a0bfa153","book_name":"css","content":"js test 3","public":false}`, "action 6 data mismatch")
assert.Equal(t, a6.timestamp, 1536977237, "action 6 timestamp mismatch")
testutils.AssertEqual(t, a7.uuid, "7f173cef-1688-4177-a373-145fcd822b2f", "action 7 uuid mismatch")
testutils.AssertEqual(t, a7.schema, 2, "action 7 schema mismatch")
testutils.AssertEqual(t, a7.actionType, "edit_note", "action 7 type mismatch")
testutils.AssertEqual(t, a7.data, `{"note_uuid":"d69edb54-5b31-4cdd-a4a5-34f0a0bfa153","from_book":"css","to_book":null,"content":"css test 1","public":null}`, "action 7 data mismatch")
testutils.AssertEqual(t, a7.timestamp, 1536977253, "action 7 timestamp mismatch")
assert.Equal(t, a7.uuid, "7f173cef-1688-4177-a373-145fcd822b2f", "action 7 uuid mismatch")
assert.Equal(t, a7.schema, 2, "action 7 schema mismatch")
assert.Equal(t, a7.actionType, "edit_note", "action 7 type mismatch")
assert.Equal(t, a7.data, `{"note_uuid":"d69edb54-5b31-4cdd-a4a5-34f0a0bfa153","from_book":"css","to_book":null,"content":"css test 1","public":null}`, "action 7 data mismatch")
assert.Equal(t, a7.timestamp, 1536977253, "action 7 timestamp mismatch")
testutils.AssertEqual(t, a8.uuid, "64352e08-aa7a-45f4-b760-b3f38b5e11fa", "action 8 uuid mismatch")
testutils.AssertEqual(t, a8.schema, 1, "action 8 schema mismatch")
testutils.AssertEqual(t, a8.actionType, "add_book", "action 8 type mismatch")
testutils.AssertEqual(t, a8.data, `{"book_name":"sql"}`, "action 8 data mismatch")
testutils.AssertEqual(t, a8.timestamp, 1536977261, "action 8 timestamp mismatch")
assert.Equal(t, a8.uuid, "64352e08-aa7a-45f4-b760-b3f38b5e11fa", "action 8 uuid mismatch")
assert.Equal(t, a8.schema, 1, "action 8 schema mismatch")
assert.Equal(t, a8.actionType, "add_book", "action 8 type mismatch")
assert.Equal(t, a8.data, `{"book_name":"sql"}`, "action 8 data mismatch")
assert.Equal(t, a8.timestamp, 1536977261, "action 8 timestamp mismatch")
testutils.AssertEqual(t, a9.uuid, "82e20a12-bda8-45f7-ac42-b453b6daa5ec", "action 9 uuid mismatch")
testutils.AssertEqual(t, a9.schema, 2, "action 9 schema mismatch")
testutils.AssertEqual(t, a9.actionType, "add_note", "action 9 type mismatch")
testutils.AssertEqual(t, a9.data, `{"note_uuid":"2f47d390-685b-4b84-89ac-704c6fb8d3fb","book_name":"sql","content":"blah","public":false}`, "action 9 data mismatch")
testutils.AssertEqual(t, a9.timestamp, 1536977261, "action 9 timestamp mismatch")
assert.Equal(t, a9.uuid, "82e20a12-bda8-45f7-ac42-b453b6daa5ec", "action 9 uuid mismatch")
assert.Equal(t, a9.schema, 2, "action 9 schema mismatch")
assert.Equal(t, a9.actionType, "add_note", "action 9 type mismatch")
assert.Equal(t, a9.data, `{"note_uuid":"2f47d390-685b-4b84-89ac-704c6fb8d3fb","book_name":"sql","content":"blah","public":false}`, "action 9 data mismatch")
assert.Equal(t, a9.timestamp, 1536977261, "action 9 timestamp mismatch")
testutils.AssertEqual(t, a10.uuid, "a29055f4-ace4-44fd-8800-3396edbccaef", "action 10 uuid mismatch")
testutils.AssertEqual(t, a10.schema, 1, "action 10 schema mismatch")
testutils.AssertEqual(t, a10.actionType, "remove_book", "action 10 type mismatch")
testutils.AssertEqual(t, a10.data, `{"book_name":"sql"}`, "action 10 data mismatch")
testutils.AssertEqual(t, a10.timestamp, 1536977268, "action 10 timestamp mismatch")
assert.Equal(t, a10.uuid, "a29055f4-ace4-44fd-8800-3396edbccaef", "action 10 uuid mismatch")
assert.Equal(t, a10.schema, 1, "action 10 schema mismatch")
assert.Equal(t, a10.actionType, "remove_book", "action 10 type mismatch")
assert.Equal(t, a10.data, `{"book_name":"sql"}`, "action 10 data mismatch")
assert.Equal(t, a10.timestamp, 1536977268, "action 10 timestamp mismatch")
testutils.AssertEqual(t, a11.uuid, "871a5562-1bd0-43c1-b550-5bbb727ac7c4", "action 11 uuid mismatch")
testutils.AssertEqual(t, a11.schema, 1, "action 11 schema mismatch")
testutils.AssertEqual(t, a11.actionType, "remove_note", "action 11 type mismatch")
testutils.AssertEqual(t, a11.data, `{"note_uuid":"b23a88ba-b291-4294-9795-86b394db5dcf","book_name":"js"}`, "action 11 data mismatch")
testutils.AssertEqual(t, a11.timestamp, 1536977274, "action 11 timestamp mismatch")
assert.Equal(t, a11.uuid, "871a5562-1bd0-43c1-b550-5bbb727ac7c4", "action 11 uuid mismatch")
assert.Equal(t, a11.schema, 1, "action 11 schema mismatch")
assert.Equal(t, a11.actionType, "remove_note", "action 11 type mismatch")
assert.Equal(t, a11.data, `{"note_uuid":"b23a88ba-b291-4294-9795-86b394db5dcf","book_name":"js"}`, "action 11 data mismatch")
assert.Equal(t, a11.timestamp, 1536977274, "action 11 timestamp mismatch")
// 3. test if system is migrated
var systemCount int
@ -586,7 +601,7 @@ func TestMigrateToV8(t *testing.T) {
panic(errors.Wrap(err, "counting system"))
}
testutils.AssertEqual(t, systemCount, 3, "action count mismatch")
assert.Equal(t, systemCount, 3, "action count mismatch")
var lastUpgrade, lastAction, bookmark int
err = db.QueryRow("SELECT value FROM system WHERE key = ?", "last_upgrade").Scan(&lastUpgrade)
@ -602,7 +617,7 @@ func TestMigrateToV8(t *testing.T) {
panic(errors.Wrap(err, "finding bookmark"))
}
testutils.AssertEqual(t, lastUpgrade, 1536977220, "last_upgrade mismatch")
testutils.AssertEqual(t, lastAction, 1536977274, "last_action mismatch")
testutils.AssertEqual(t, bookmark, 9, "bookmark mismatch")
assert.Equal(t, lastUpgrade, 1536977220, "last_upgrade mismatch")
assert.Equal(t, lastAction, 1536977274, "last_action mismatch")
assert.Equal(t, bookmark, 9, "bookmark mismatch")
}

View file

@ -20,8 +20,9 @@ package migrate
import (
"database/sql"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/pkg/errors"
)
@ -52,7 +53,7 @@ var RemoteSequence = []migration{
rm1,
}
func initSchema(ctx infra.DnoteCtx, schemaKey string) (int, error) {
func initSchema(ctx context.DnoteCtx, schemaKey string) (int, error) {
// schemaVersion is the index of the latest run migration in the sequence
schemaVersion := 0
@ -67,17 +68,17 @@ func initSchema(ctx infra.DnoteCtx, schemaKey string) (int, error) {
func getSchemaKey(mode int) (string, error) {
if mode == LocalMode {
return infra.SystemSchema, nil
return consts.SystemSchema, nil
}
if mode == RemoteMode {
return infra.SystemRemoteSchema, nil
return consts.SystemRemoteSchema, nil
}
return "", errors.Errorf("unsupported migration type '%d'", mode)
}
func getSchema(ctx infra.DnoteCtx, schemaKey string) (int, error) {
func getSchema(ctx context.DnoteCtx, schemaKey string) (int, error) {
var ret int
db := ctx.DB
@ -95,7 +96,7 @@ func getSchema(ctx infra.DnoteCtx, schemaKey string) (int, error) {
return ret, nil
}
func execute(ctx infra.DnoteCtx, m migration, schemaKey string) error {
func execute(ctx context.DnoteCtx, m migration, schemaKey string) error {
log.Debug("running migration %s\n", m.name)
tx, err := ctx.DB.Begin()
@ -128,7 +129,7 @@ func execute(ctx infra.DnoteCtx, m migration, schemaKey string) error {
}
// Run performs unrun migrations
func Run(ctx infra.DnoteCtx, migrations []migration, mode int) error {
func Run(ctx context.DnoteCtx, migrations []migration, mode int) error {
schemaKey, err := getSchemaKey(mode)
if err != nil {
return errors.Wrap(err, "getting schema key")
@ -139,7 +140,7 @@ func Run(ctx infra.DnoteCtx, migrations []migration, mode int) error {
return errors.Wrap(err, "getting the current schema")
}
log.Debug("current schema: %s %d of %d\n", infra.SystemSchema, schema, len(migrations))
log.Debug("current schema: %s %d of %d\n", consts.SystemSchema, schema, len(migrations))
toRun := migrations[schema:]

File diff suppressed because it is too large Load diff

View file

@ -26,20 +26,21 @@ import (
"strings"
"github.com/dnote/actions"
"github.com/dnote/dnote/cli/client"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/pkg/cli/client"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/pkg/errors"
)
type migration struct {
name string
run func(ctx infra.DnoteCtx, tx *infra.DB) error
run func(ctx context.DnoteCtx, tx *database.DB) error
}
var lm1 = migration{
name: "upgrade-edit-note-from-v1-to-v3",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
rows, err := tx.Query("SELECT uuid, data FROM actions WHERE type = ? AND schema = ?", "edit_note", 1)
if err != nil {
return errors.Wrap(err, "querying rows")
@ -87,7 +88,7 @@ var lm1 = migration{
var lm2 = migration{
name: "upgrade-edit-note-from-v2-to-v3",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
rows, err := tx.Query("SELECT uuid, data FROM actions WHERE type = ? AND schema = ?", "edit_note", 2)
if err != nil {
return errors.Wrap(err, "querying rows")
@ -132,7 +133,7 @@ var lm2 = migration{
var lm3 = migration{
name: "upgrade-remove-note-from-v1-to-v2",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
rows, err := tx.Query("SELECT uuid, data FROM actions WHERE type = ? AND schema = ?", "remove_note", 1)
if err != nil {
return errors.Wrap(err, "querying rows")
@ -174,7 +175,7 @@ var lm3 = migration{
var lm4 = migration{
name: "add-dirty-usn-deleted-to-notes-and-books",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
_, err := tx.Exec("ALTER TABLE books ADD COLUMN dirty bool DEFAULT false")
if err != nil {
return errors.Wrap(err, "adding dirty column to books")
@ -211,7 +212,7 @@ var lm4 = migration{
var lm5 = migration{
name: "mark-action-targets-dirty",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
rows, err := tx.Query("SELECT uuid, data, type FROM actions")
if err != nil {
return errors.Wrap(err, "querying rows")
@ -273,7 +274,7 @@ var lm5 = migration{
var lm6 = migration{
name: "drop-actions",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
_, err := tx.Exec("DROP TABLE actions;")
if err != nil {
return errors.Wrap(err, "dropping the actions table")
@ -285,7 +286,7 @@ var lm6 = migration{
var lm7 = migration{
name: "resolve-conflicts-with-reserved-book-names",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
migrateBook := func(name string) error {
var uuid string
@ -332,7 +333,7 @@ var lm7 = migration{
var lm8 = migration{
name: "drop-note-id-and-rename-content-to-body",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
_, err := tx.Exec(`CREATE TABLE notes_tmp
(
uuid text NOT NULL,
@ -371,7 +372,7 @@ var lm8 = migration{
var lm9 = migration{
name: "create-fts-index",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
_, err := tx.Exec(`CREATE VIRTUAL TABLE IF NOT EXISTS note_fts USING fts5(content=notes, body, tokenize="porter unicode61 categories 'L* N* Co Ps Pe'");`)
if err != nil {
return errors.Wrap(err, "creating note_fts")
@ -407,7 +408,7 @@ var lm9 = migration{
var lm10 = migration{
name: "rename-number-only-book",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
migrateBook := func(label string) error {
var uuid string
@ -467,7 +468,7 @@ var lm10 = migration{
var lm11 = migration{
name: "rename-book-labels-with-space",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
processLabel := func(label string) (string, error) {
sanitized := strings.Replace(label, " ", "_", -1)
@ -531,7 +532,7 @@ var lm11 = migration{
var rm1 = migration{
name: "sync-book-uuids-from-server",
run: func(ctx infra.DnoteCtx, tx *infra.DB) error {
run: func(ctx context.DnoteCtx, tx *database.DB) error {
sessionKey := ctx.SessionKey
if sessionKey == "" {
return errors.New("not logged in")

View file

@ -16,17 +16,20 @@
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package core
// Package output provides functions to print informations on the terminal
// in a consistent manner
package output
import (
"fmt"
"time"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/log"
)
// PrintNoteInfo prints a note information
func PrintNoteInfo(info NoteInfo) {
// NoteInfo prints a note information
func NoteInfo(info database.NoteInfo) {
log.Infof("book name: %s\n", info.BookLabel)
log.Infof("created at: %s\n", time.Unix(0, info.AddedOn).Format("Jan 2, 2006 3:04pm (MST)"))
if info.EditedOn != 0 {

View file

@ -9,7 +9,7 @@ set -eu
version="$1"
projectDir="$GOPATH/src/github.com/dnote/dnote"
basedir="$GOPATH/src/github.com/dnote/dnote/cli"
basedir="$GOPATH/src/github.com/dnote/dnote/pkg/cli"
TMP="$basedir/build"
command_exists () {

View file

@ -6,6 +6,6 @@ set -eux
sudo rm -rf "$(which dnote)" "$GOPATH/bin/cli"
# change tags to darwin if on macos
go install -ldflags "-X main.apiEndpoint=http://127.0.0.1:5000" --tags "linux fts5" "$GOPATH/src/github.com/dnote/dnote/cli/."
go install -ldflags "-X main.apiEndpoint=http://127.0.0.1:5000" --tags "linux fts5" "$GOPATH/src/github.com/dnote/dnote/pkg/cli/."
sudo ln -s "$GOPATH/bin/cli" /usr/local/bin/dnote

View file

@ -4,7 +4,7 @@
set -eux
basePath="$GOPATH/src/github.com/dnote/dnote/cli"
basePath="$GOPATH/src/github.com/dnote/dnote/pkg/cli"
# clear tmp dir in case not properly torn down
rm -rf "$basePath/tmp"

228
pkg/cli/testutils/main.go Normal file
View file

@ -0,0 +1,228 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
// Package testutils provides utilities used in tests
package testutils
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/utils"
"github.com/pkg/errors"
)
// Login simulates a logged in user by inserting credentials in the local database
func Login(t *testing.T, ctx *context.DnoteCtx) {
db := ctx.DB
database.MustExec(t, "inserting sessionKey", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKey, "someSessionKey")
database.MustExec(t, "inserting sessionKeyExpiry", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKeyExpiry, time.Now().Add(24*time.Hour).Unix())
database.MustExec(t, "inserting cipherKey", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemCipherKey, "QUVTMjU2S2V5LTMyQ2hhcmFjdGVyczEyMzQ1Njc4OTA=")
ctx.SessionKey = "someSessionKey"
ctx.SessionKeyExpiry = time.Now().Add(24 * time.Hour).Unix()
ctx.CipherKey = []byte("AES256Key-32Characters1234567890")
}
// RemoveDir cleans up the test env represented by the given context
func RemoveDir(t *testing.T, dir string) {
if err := os.RemoveAll(dir); err != nil {
t.Fatal(errors.Wrap(err, "removing the directory"))
}
}
// CopyFixture writes the content of the given fixture to the filename inside the dnote dir
func CopyFixture(t *testing.T, ctx context.DnoteCtx, fixturePath string, filename string) {
fp, err := filepath.Abs(fixturePath)
if err != nil {
t.Fatal(errors.Wrap(err, "getting the absolute path for fixture"))
}
dp, err := filepath.Abs(filepath.Join(ctx.DnoteDir, filename))
if err != nil {
t.Fatal(errors.Wrap(err, "getting the absolute path dnote dir"))
}
err = utils.CopyFile(fp, dp)
if err != nil {
t.Fatal(errors.Wrap(err, "copying the file"))
}
}
// WriteFile writes a file with the given content and filename inside the dnote dir
func WriteFile(ctx context.DnoteCtx, content []byte, filename string) {
dp, err := filepath.Abs(filepath.Join(ctx.DnoteDir, filename))
if err != nil {
panic(err)
}
if err := ioutil.WriteFile(dp, content, 0644); err != nil {
panic(err)
}
}
// ReadFile reads the content of the file with the given name in dnote dir
func ReadFile(ctx context.DnoteCtx, filename string) []byte {
path := filepath.Join(ctx.DnoteDir, filename)
b, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
return b
}
// ReadJSON reads JSON fixture to the struct at the destination address
func ReadJSON(path string, destination interface{}) {
var dat []byte
dat, err := ioutil.ReadFile(path)
if err != nil {
panic(errors.Wrap(err, "Failed to load fixture payload"))
}
if err := json.Unmarshal(dat, destination); err != nil {
panic(errors.Wrap(err, "Failed to get event"))
}
}
// NewDnoteCmd returns a new Dnote command and a pointer to stderr
func NewDnoteCmd(opts RunDnoteCmdOptions, binaryName string, arg ...string) (*exec.Cmd, *bytes.Buffer, *bytes.Buffer, error) {
var stderr, stdout bytes.Buffer
binaryPath, err := filepath.Abs(binaryName)
if err != nil {
return &exec.Cmd{}, &stderr, &stdout, errors.Wrap(err, "getting the absolute path to the test binary")
}
cmd := exec.Command(binaryPath, arg...)
cmd.Env = []string{fmt.Sprintf("DNOTE_DIR=%s", opts.DnoteDir), fmt.Sprintf("DNOTE_HOME_DIR=%s", opts.HomeDir)}
cmd.Stderr = &stderr
cmd.Stdout = &stdout
return cmd, &stderr, &stdout, nil
}
// RunDnoteCmdOptions is an option for RunDnoteCmd
type RunDnoteCmdOptions struct {
DnoteDir string
HomeDir string
}
// RunDnoteCmd runs a dnote command
func RunDnoteCmd(t *testing.T, opts RunDnoteCmdOptions, binaryName string, arg ...string) {
t.Logf("running: %s %s", binaryName, strings.Join(arg, " "))
cmd, stderr, stdout, err := NewDnoteCmd(opts, binaryName, arg...)
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrap(err, "getting command").Error())
}
cmd.Env = append(cmd.Env, "DNOTE_DEBUG=1")
if err := cmd.Run(); err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrapf(err, "running command %s", stderr.String()))
}
// Print stdout if and only if test fails later
t.Logf("\n%s", stdout)
}
// WaitDnoteCmd runs a dnote command and waits until the command is exited
func WaitDnoteCmd(t *testing.T, opts RunDnoteCmdOptions, runFunc func(io.WriteCloser) error, binaryName string, arg ...string) {
t.Logf("running: %s %s", binaryName, strings.Join(arg, " "))
cmd, stderr, stdout, err := NewDnoteCmd(opts, binaryName, arg...)
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrap(err, "getting command").Error())
}
stdin, err := cmd.StdinPipe()
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrap(err, "getting stdin %s"))
}
defer stdin.Close()
// Start the program
err = cmd.Start()
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrap(err, "starting command"))
}
err = runFunc(stdin)
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrap(err, "running with stdin"))
}
err = cmd.Wait()
if err != nil {
t.Logf("\n%s", stdout)
t.Fatal(errors.Wrapf(err, "running command %s", stderr.String()))
}
// Print stdout if and only if test fails later
t.Logf("\n%s", stdout)
}
// UserConfirm simulates confirmation from the user by writing to stdin
func UserConfirm(stdin io.WriteCloser) error {
// confirm
if _, err := io.WriteString(stdin, "y\n"); err != nil {
return errors.Wrap(err, "indicating confirmation in stdin")
}
return nil
}
// MustMarshalJSON marshalls the given interface into JSON.
// If there is any error, it fails the test.
func MustMarshalJSON(t *testing.T, v interface{}) []byte {
b, err := json.Marshal(v)
if err != nil {
t.Fatalf("%s: marshalling data", t.Name())
}
return b
}
// MustUnmarshalJSON marshalls the given interface into JSON.
// If there is any error, it fails the test.
func MustUnmarshalJSON(t *testing.T, data []byte, v interface{}) {
err := json.Unmarshal(data, v)
if err != nil {
t.Fatalf("%s: unmarshalling data", t.Name())
}
}

View file

@ -0,0 +1,71 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package testutils
import (
"testing"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/database"
)
// Setup1 sets up a dnote env #1
func Setup1(t *testing.T, ctx context.DnoteCtx) {
db := ctx.DB
b1UUID := "js-book-uuid"
b2UUID := "linux-book-uuid"
database.MustExec(t, "setting up book 1", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "js")
database.MustExec(t, "setting up book 2", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b2UUID, "linux")
database.MustExec(t, "setting up note 1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "43827b9a-c2b0-4c06-a290-97991c896653", b1UUID, "Booleans have toString()", 1515199943)
}
// Setup2 sets up a dnote env #2
func Setup2(t *testing.T, db *database.DB) {
b1UUID := "js-book-uuid"
b2UUID := "linux-book-uuid"
database.MustExec(t, "setting up book 1", db, "INSERT INTO books (uuid, label, usn) VALUES (?, ?, ?)", b1UUID, "js", 111)
database.MustExec(t, "setting up book 2", db, "INSERT INTO books (uuid, label, usn) VALUES (?, ?, ?)", b2UUID, "linux", 122)
database.MustExec(t, "setting up note 1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, usn) VALUES (?, ?, ?, ?, ?)", "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", b1UUID, "n1 body", 1515199951, 11)
database.MustExec(t, "setting up note 2", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, usn) VALUES (?, ?, ?, ?, ?)", "43827b9a-c2b0-4c06-a290-97991c896653", b1UUID, "n2 body", 1515199943, 12)
database.MustExec(t, "setting up note 3", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, usn) VALUES (?, ?, ?, ?, ?)", "3e065d55-6d47-42f2-a6bf-f5844130b2d2", b2UUID, "n3 body", 1515199961, 13)
}
// Setup3 sets up a dnote env #3
func Setup3(t *testing.T, db *database.DB) {
b1UUID := "js-book-uuid"
database.MustExec(t, "setting up book 1", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "js")
database.MustExec(t, "setting up note 1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?)", "43827b9a-c2b0-4c06-a290-97991c896653", b1UUID, "Booleans have toString()", 1515199943)
}
// Setup4 sets up a dnote env #4
func Setup4(t *testing.T, db *database.DB) {
b1UUID := "js-book-uuid"
database.MustExec(t, "setting up book 1", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "js")
database.MustExec(t, "setting up note 1", db, "INSERT INTO notes (rowid, uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?, ?)", 1, "43827b9a-c2b0-4c06-a290-97991c896653", b1UUID, "Booleans have toString()", 1515199943)
database.MustExec(t, "setting up note 2", db, "INSERT INTO notes (rowid, uuid, book_uuid, body, added_on) VALUES (?, ?, ?, ?, ?)", 2, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", b1UUID, "Date object implements mathematical comparisons", 1515199951)
}

146
pkg/cli/ui/editor.go Normal file
View file

@ -0,0 +1,146 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
// Package ui provides the user interface for the program
package ui
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/utils"
"github.com/pkg/errors"
)
// GetTmpContentPath returns the path to the temporary file containing
// content being added or edited
func GetTmpContentPath(ctx context.DnoteCtx) string {
return fmt.Sprintf("%s/%s", ctx.DnoteDir, consts.TmpContentFilename)
}
// getEditorCommand returns the system's editor command with appropriate flags,
// if necessary, to make the command wait until editor is close to exit.
func getEditorCommand() string {
editor := os.Getenv("EDITOR")
var ret string
switch editor {
case "atom":
ret = "atom -w"
case "subl":
ret = "subl -n -w"
case "mate":
ret = "mate -w"
case "vim":
ret = "vim"
case "nano":
ret = "nano"
case "emacs":
ret = "emacs"
case "nvim":
ret = "nvim"
default:
ret = "vi"
}
return ret
}
// SanitizeContent sanitizes note content
func SanitizeContent(s string) string {
var ret string
ret = strings.Trim(s, " ")
// Remove newline at the end of the file because POSIX defines a line as
// characters followed by a newline
ret = strings.TrimSuffix(ret, "\n")
ret = strings.TrimSuffix(ret, "\r\n")
return ret
}
func newEditorCmd(ctx context.DnoteCtx, fpath string) (*exec.Cmd, error) {
config, err := infra.ReadConfig(ctx)
if err != nil {
return nil, errors.Wrap(err, "reading config")
}
args := strings.Fields(config.Editor)
args = append(args, fpath)
return exec.Command(args[0], args[1:]...), nil
}
// GetEditorInput gets the user input by launching a text editor and waiting for
// it to exit
func GetEditorInput(ctx context.DnoteCtx, fpath string, content *string) error {
if !utils.FileExists(fpath) {
f, err := os.Create(fpath)
if err != nil {
return errors.Wrap(err, "creating a temporary content file")
}
err = f.Close()
if err != nil {
return errors.Wrap(err, "closing the temporary content file")
}
}
cmd, err := newEditorCmd(ctx, fpath)
if err != nil {
return errors.Wrap(err, "creating an editor command")
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Start()
if err != nil {
return errors.Wrapf(err, "launching an editor")
}
err = cmd.Wait()
if err != nil {
return errors.Wrap(err, "waiting for the editor")
}
b, err := ioutil.ReadFile(fpath)
if err != nil {
return errors.Wrap(err, "reading the temporary content file")
}
err = os.Remove(fpath)
if err != nil {
return errors.Wrap(err, "removing the temporary content file")
}
raw := string(b)
c := SanitizeContent(raw)
*content = c
return nil
}

97
pkg/cli/ui/terminal.go Normal file
View file

@ -0,0 +1,97 @@
/* Copyright (C) 2019 Monomax Software Pty Ltd
*
* This file is part of Dnote CLI.
*
* Dnote CLI is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dnote CLI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package ui
import (
"bufio"
"fmt"
"os"
"strings"
"syscall"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh/terminal"
)
func readInput() (string, error) {
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
return "", errors.Wrap(err, "reading stdin")
}
return strings.Trim(input, "\r\n"), nil
}
// PromptInput prompts the user input and saves the result to the destination
func PromptInput(message string, dest *string) error {
log.Askf(message, false)
input, err := readInput()
if err != nil {
return errors.Wrap(err, "getting user input")
}
*dest = input
return nil
}
// PromptPassword prompts the user input a password and saves the result to the destination.
// The input is masked, meaning it is not echoed on the terminal.
func PromptPassword(message string, dest *string) error {
log.Askf(message, true)
password, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
return errors.Wrap(err, "getting user input")
}
fmt.Println("")
*dest = string(password)
return nil
}
// Confirm prompts for user input to confirm a choice
func Confirm(question string, optimistic bool) (bool, error) {
var choices string
if optimistic {
choices = "(Y/n)"
} else {
choices = "(y/N)"
}
message := fmt.Sprintf("%s %s", question, choices)
var input string
if err := PromptInput(message, &input); err != nil {
return false, errors.Wrap(err, "Failed to get user input")
}
confirmed := input == "y"
if optimistic {
confirmed = confirmed || input == ""
}
return confirmed, nil
}

View file

@ -16,16 +16,17 @@
* along with Dnote CLI. If not, see <https://www.gnu.org/licenses/>.
*/
package core
package upgrade
import (
"context"
stdCtx "context"
"fmt"
"time"
"github.com/dnote/dnote/cli/infra"
"github.com/dnote/dnote/cli/log"
"github.com/dnote/dnote/cli/utils"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/context"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/dnote/dnote/pkg/cli/ui"
"github.com/google/go-github/github"
"github.com/pkg/errors"
)
@ -34,11 +35,11 @@ import (
var upgradeInterval int64 = 86400 * 7 * 3
// shouldCheckUpdate checks if update should be checked
func shouldCheckUpdate(ctx infra.DnoteCtx) (bool, error) {
func shouldCheckUpdate(ctx context.DnoteCtx) (bool, error) {
db := ctx.DB
var lastUpgrade int64
err := db.QueryRow("SELECT value FROM system WHERE key = ?", infra.SystemLastUpgrade).Scan(&lastUpgrade)
err := db.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemLastUpgrade).Scan(&lastUpgrade)
if err != nil {
return false, errors.Wrap(err, "getting last_udpate")
}
@ -48,11 +49,11 @@ func shouldCheckUpdate(ctx infra.DnoteCtx) (bool, error) {
return now-lastUpgrade > upgradeInterval, nil
}
func touchLastUpgrade(ctx infra.DnoteCtx) error {
func touchLastUpgrade(ctx context.DnoteCtx) error {
db := ctx.DB
now := time.Now().Unix()
_, err := db.Exec("UPDATE system SET value = ? WHERE key = ?", now, infra.SystemLastUpgrade)
_, err := db.Exec("UPDATE system SET value = ? WHERE key = ?", now, consts.SystemLastUpgrade)
if err != nil {
return errors.Wrap(err, "updating last_upgrade")
}
@ -60,32 +61,34 @@ func touchLastUpgrade(ctx infra.DnoteCtx) error {
return nil
}
func checkVersion(ctx infra.DnoteCtx) error {
func checkVersion(ctx context.DnoteCtx) error {
log.Infof("current version is %s\n", ctx.Version)
// Fetch the latest version
gh := github.NewClient(nil)
releases, _, err := gh.Repositories.ListReleases(context.Background(), "dnote", "cli", nil)
releases, _, err := gh.Repositories.ListReleases(stdCtx.Background(), "dnote", "cli", nil)
if err != nil {
return errors.Wrap(err, "fetching releases")
}
latest := releases[0]
latestVersion := (*latest.TagName)[1:]
// releases are tagged in a form of cli-v1.0.0
latestVersion := (*latest.TagName)[5:]
log.Infof("latest version is %s\n", latestVersion)
if latestVersion == ctx.Version {
log.Success("you are up-to-date\n\n")
} else {
log.Infof("to upgrade, see https://github.com/dnote/dnote/cli/blob/master/README.md\n")
log.Infof("to upgrade, see https://github.com/dnote/dnote/pkg/cli/blob/master/README.md\n")
}
return nil
}
// CheckUpdate triggers update if needed
func CheckUpdate(ctx infra.DnoteCtx) error {
// Check triggers update if needed
func Check(ctx context.DnoteCtx) error {
shouldCheck, err := shouldCheckUpdate(ctx)
if err != nil {
return errors.Wrap(err, "checking if dnote should check update")
@ -100,7 +103,7 @@ func CheckUpdate(ctx infra.DnoteCtx) error {
}
fmt.Printf("\n")
willCheck, err := utils.AskConfirmation("check for upgrade?", true)
willCheck, err := ui.Confirm("check for upgrade?", true)
if err != nil {
return errors.Wrap(err, "getting user confirmation")
}

View file

@ -22,7 +22,7 @@ import (
"fmt"
"testing"
"github.com/dnote/dnote/cli/testutils"
"github.com/dnote/dnote/pkg/assert"
"github.com/sergi/go-diff/diffmatchpatch"
)
@ -143,7 +143,7 @@ func TestDo(t *testing.T) {
result := Do(tc.s1, tc.s2)
t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) {
testutils.AssertDeepEqual(t, result, tc.expected, "result mismatch")
assert.DeepEqual(t, result, tc.expected, "result mismatch")
})
}
}

View file

@ -19,92 +19,28 @@
package utils
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"syscall"
"github.com/dnote/dnote/cli/log"
"github.com/pkg/errors"
"github.com/satori/go.uuid"
"golang.org/x/crypto/ssh/terminal"
)
// GenerateUUID returns a uid
func GenerateUUID() string {
return uuid.NewV4().String()
}
func getInput() (string, error) {
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
// ReadFileAbs reads the content of the file with the given file path by resolving
// it as an absolute path
func ReadFileAbs(relpath string) []byte {
fp, err := filepath.Abs(relpath)
if err != nil {
return "", errors.Wrap(err, "reading stdin")
panic(err)
}
return strings.Trim(input, "\r\n"), nil
}
// PromptInput prompts the user input and saves the result to the destination
func PromptInput(message string, dest *string) error {
log.Askf(message, false)
input, err := getInput()
b, err := ioutil.ReadFile(fp)
if err != nil {
return errors.Wrap(err, "getting user input")
panic(err)
}
*dest = input
return nil
}
// PromptPassword prompts the user input a password and saves the result to the destination.
// The input is masked, meaning it is not echoed on the terminal.
func PromptPassword(message string, dest *string) error {
log.Askf(message, true)
password, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
return errors.Wrap(err, "getting user input")
}
fmt.Println("")
*dest = string(password)
return nil
}
// AskConfirmation prompts for user input to confirm a choice
func AskConfirmation(question string, optimistic bool) (bool, error) {
var choices string
if optimistic {
choices = "(Y/n)"
} else {
choices = "(y/N)"
}
message := fmt.Sprintf("%s %s", question, choices)
var input string
if err := PromptInput(message, &input); err != nil {
return false, errors.Wrap(err, "Failed to get user input")
}
confirmed := input == "y"
if optimistic {
confirmed = confirmed || input == ""
}
return confirmed, nil
return b
}
// FileExists checks if the file exists at the given path
@ -160,22 +96,6 @@ func CopyDir(src, dest string) error {
return nil
}
// checkRespErr checks if the given http response indicates an error. It returns a boolean indicating
// if the response is an error, and a decoded error message.
func checkRespErr(res *http.Response) error {
if res.StatusCode < 400 {
return nil
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrapf(err, "server responded with %d but could not read the response body", res.StatusCode)
}
bodyStr := string(body)
return errors.Errorf(`response %d "%s"`, res.StatusCode, strings.TrimRight(bodyStr, "\n"))
}
// CopyFile copies a file from the src to dest
func CopyFile(src, dest string) error {
in, err := os.Open(src)
@ -213,15 +133,3 @@ func CopyFile(src, dest string) error {
return nil
}
// regexNumber is a regex that matches a string that looks like an integer
var regexNumber = regexp.MustCompile(`^\d+$`)
// IsNumber checks if the given string is in the form of a number
func IsNumber(s string) bool {
if s == "" {
return false
}
return regexNumber.MatchString(s)
}

Some files were not shown because too many files have changed in this diff Show more