mirror of
https://github.com/dnote/dnote
synced 2026-03-14 22:45:50 +01:00
Improve package structure (#207)
* Improve package structure * Set up travis
This commit is contained in:
parent
ca03c8576a
commit
23a511dbe0
171 changed files with 4750 additions and 4531 deletions
25
.travis.yml
Normal file
25
.travis.yml
Normal 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
56
Makefile
Normal 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
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
Dnote is a simple notebook for developers.
|
||||
|
||||
[](https://semaphoreci.com/dnote/dnote-2)
|
||||
[](https://travis-ci.org/dnote/dnote)
|
||||
|
||||
## What is Dnote?
|
||||
|
||||
|
|
|
|||
358
cli/core/core.go
358
cli/core/core.go
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
80
cli/main.go
80
cli/main.go
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
339
cli/main_test.go
339
cli/main_test.go
|
|
@ -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"), ¬esTableCount)
|
||||
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"), ¬eCount)
|
||||
|
||||
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), ¬e.UUID, ¬e.Body, ¬e.AddedOn, ¬e.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"), ¬eCount)
|
||||
|
||||
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"), ¬eCount)
|
||||
|
||||
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"), ¬eCount)
|
||||
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"), ¬eCount)
|
||||
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
|
|
@ -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);
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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
123
pkg/assert/assert.go
Normal 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))
|
||||
}
|
||||
}
|
||||
0
cli/.gitignore → pkg/cli/.gitignore
vendored
0
cli/.gitignore → pkg/cli/.gitignore
vendored
18
cli/Gopkg.lock → pkg/cli/Gopkg.lock
generated
18
cli/Gopkg.lock → pkg/cli/Gopkg.lock
generated
|
|
@ -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",
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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 == "" {
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 ¬eMergeReport{
|
||||
body: serverNote.Body,
|
||||
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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(¬e.UUID, ¬e.USN, ¬e.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(¬e.UUID, ¬e.BookUUID, ¬e.Body, ¬e.Public, ¬e.Deleted, ¬e.USN, ¬e.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
|
|
@ -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",
|
||||
|
|
@ -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
46
pkg/cli/consts/consts.go
Normal 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
36
pkg/cli/context/ctx.go
Normal 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
|
||||
}
|
||||
48
pkg/cli/context/operations.go
Normal file
48
pkg/cli/context/operations.go
Normal 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
|
||||
}
|
||||
41
pkg/cli/context/testutils.go
Normal file
41
pkg/cli/context/testutils.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
@ -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"), ¬eCount)
|
||||
MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), ¬eCount)
|
||||
|
||||
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"), ¬eCount)
|
||||
testutils.MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), ¬eFtsCount)
|
||||
testutils.MustScan(t, "counting search results", db.QueryRow("SELECT count(*) FROM note_fts WHERE note_fts MATCH ?", "foo"), ¬eSearchCount)
|
||||
MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), ¬eCount)
|
||||
MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), ¬eFtsCount)
|
||||
MustScan(t, "counting search results", db.QueryRow("SELECT count(*) FROM note_fts WHERE note_fts MATCH ?", "foo"), ¬eSearchCount)
|
||||
|
||||
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"), ¬eCount)
|
||||
testutils.MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), ¬eFtsCount)
|
||||
testutils.AssertEqual(t, noteCount, 1, "noteCount mismatch")
|
||||
testutils.AssertEqual(t, noteFtsCount, 1, "noteFtsCount mismatch")
|
||||
MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), ¬eCount)
|
||||
MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), ¬eFtsCount)
|
||||
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"), ¬eSearchCount)
|
||||
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"), ¬eSearchCount)
|
||||
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"), ¬eSearchCount)
|
||||
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"), ¬eSearchCount)
|
||||
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"), ¬eCount)
|
||||
testutils.MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), ¬eFtsCount)
|
||||
MustScan(t, "counting notes", db.QueryRow("SELECT count(*) FROM notes"), ¬eCount)
|
||||
MustScan(t, "counting note_fts", db.QueryRow("SELECT count(*) FROM note_fts"), ¬eFtsCount)
|
||||
|
||||
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")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
170
pkg/cli/database/testutils.go
Normal file
170
pkg/cli/database/testutils.go
Normal 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
56
pkg/cli/infra/config.go
Normal 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
365
pkg/cli/infra/init.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
71
pkg/cli/main.go
Normal 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
333
pkg/cli/main_test.go
Normal 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"), ¬esTableCount)
|
||||
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"), ¬eCount)
|
||||
|
||||
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), ¬e.UUID, ¬e.Body, ¬e.AddedOn, ¬e.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"), ¬eCount)
|
||||
|
||||
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"), ¬eCount)
|
||||
|
||||
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"), ¬eCount)
|
||||
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"), ¬eCount)
|
||||
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")
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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:]
|
||||
|
||||
1134
pkg/cli/migrate/migrate_test.go
Normal file
1134
pkg/cli/migrate/migrate_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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")
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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 () {
|
||||
|
|
@ -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
|
||||
|
|
@ -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
228
pkg/cli/testutils/main.go
Normal 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())
|
||||
}
|
||||
}
|
||||
71
pkg/cli/testutils/setup.go
Normal file
71
pkg/cli/testutils/setup.go
Normal 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
146
pkg/cli/ui/editor.go
Normal 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
97
pkg/cli/ui/terminal.go
Normal 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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue