Implement first unit-tests #6
@ -16,19 +16,16 @@ func main() {
|
||||
log.Fatalf("Could not load config: %v", err)
|
||||
}
|
||||
|
||||
bv := &bcrypt.Verifier{}
|
||||
|
||||
q, err := postgres.Connect(cfg.DatabaseHost, cfg.DatabaseUser, cfg.DatabasePassword, cfg.DatabaseName)
|
||||
q, err := postgres.Connect("pgx", cfg.DatabaseConnection)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed connecting to DB: %v", err)
|
||||
}
|
||||
|
||||
tv := &jwt.TokenVerifier{}
|
||||
|
||||
h := &http.Handler{
|
||||
Service: q,
|
||||
TokenVerifier: tv,
|
||||
CredentialsVerifier: bv,
|
||||
TokenVerifier: &jwt.TokenVerifier{},
|
||||
CredentialsVerifier: &bcrypt.Verifier{},
|
||||
}
|
||||
|
||||
h.Serve()
|
||||
}
|
||||
|
@ -6,23 +6,13 @@ import (
|
||||
|
||||
// Config contains all needed configurations
|
||||
type Config struct {
|
||||
DatabaseUser string
|
||||
DatabaseHost string
|
||||
DatabasePassword string
|
||||
DatabaseName string
|
||||
DatabaseConnection string
|
||||
}
|
||||
|
||||
// LoadConfig from path
|
||||
func LoadConfig() (*Config, error) {
|
||||
configuration := Config{
|
||||
DatabaseUser: os.Getenv("BUDGETEER_DB_USER"),
|
||||
DatabaseHost: os.Getenv("BUDGETEER_DB_HOST"),
|
||||
DatabasePassword: os.Getenv("BUDGETEER_DB_PASS"),
|
||||
DatabaseName: os.Getenv("BUDGETEER_DB_NAME"),
|
||||
}
|
||||
|
||||
if configuration.DatabaseName == "" {
|
||||
configuration.DatabaseName = "budgeteer"
|
||||
DatabaseConnection: os.Getenv("BUDGETEER_DB"),
|
||||
}
|
||||
|
||||
return &configuration, nil
|
||||
|
@ -18,15 +18,14 @@ services:
|
||||
- ~/.go:/go
|
||||
- ~/.cache:/.cache
|
||||
environment:
|
||||
BUDGETEER_DB_NAME: budgeteer
|
||||
BUDGETEER_DB_USER: budgeteer
|
||||
BUDGETEER_DB_PASS: budgeteer
|
||||
BUDGETEER_DB_HOST: db:5432
|
||||
BUDGETEER_DB: postgres://budgeteer:budgeteer@db:5432/budgeteer
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: postgres:14
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- db:/var/lib/postgresql/data
|
||||
environment:
|
||||
|
2
go.mod
2
go.mod
@ -11,6 +11,8 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa
|
||||
)
|
||||
|
||||
require github.com/DATA-DOG/go-txdb v0.1.5 // indirect
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.13.0 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -4,6 +4,8 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/DATA-DOG/go-txdb v0.1.5 h1:kKzz+LYk9qw1+fMyo8/9yDQiNXrJ2HbfX/TY61HkkB4=
|
||||
github.com/DATA-DOG/go-txdb v0.1.5/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
|
@ -28,8 +28,10 @@ func (h *Handler) transactionsForAccount(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, struct {
|
||||
Account postgres.Account
|
||||
Transactions []postgres.GetTransactionsForAccountRow
|
||||
}{account, transactions})
|
||||
c.JSON(http.StatusOK, TransactionsResponse{account, transactions})
|
||||
}
|
||||
|
||||
type TransactionsResponse struct {
|
||||
Account postgres.Account
|
||||
Transactions []postgres.GetTransactionsForAccountRow
|
||||
}
|
||||
|
79
http/account_test.go
Normal file
79
http/account_test.go
Normal file
@ -0,0 +1,79 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.javil.eu/jacob1123/budgeteer/bcrypt"
|
||||
"git.javil.eu/jacob1123/budgeteer/jwt"
|
||||
"git.javil.eu/jacob1123/budgeteer/postgres"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
txdb "github.com/DATA-DOG/go-txdb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
txdb.Register("pgtx", "pgx", "postgres://budgeteer_test:budgeteer_test@localhost:5432/budgeteer_test")
|
||||
}
|
||||
|
||||
func TestListTimezonesHandler(t *testing.T) {
|
||||
db, err := postgres.Connect("pgtx", "example")
|
||||
if err != nil {
|
||||
t.Errorf("could not connect to db: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
h := Handler{
|
||||
Service: db,
|
||||
TokenVerifier: &jwt.TokenVerifier{},
|
||||
CredentialsVerifier: &bcrypt.Verifier{},
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
c, engine := gin.CreateTestContext(rr)
|
||||
h.LoadRoutes(engine)
|
||||
|
||||
t.Run("RegisterUser", func(t *testing.T) {
|
||||
c.Request, err = http.NewRequest(http.MethodPost, "/api/v1/user/register", strings.NewReader(`{"password":"pass","email":"info@example.com","name":"Test"}`))
|
||||
if err != nil {
|
||||
t.Errorf("error creating request: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
h.registerPost(c)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
var response LoginResponse
|
||||
err = json.NewDecoder(rr.Body).Decode(&response)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
t.Error("Error registering")
|
||||
}
|
||||
if len(response.Token) == 0 {
|
||||
t.Error("Did not get a token")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetTransactions", func(t *testing.T) {
|
||||
c.Request, err = http.NewRequest(http.MethodGet, "/account/accountid/transactions", nil)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
var response TransactionsResponse
|
||||
err = json.NewDecoder(rr.Body).Decode(&response)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
t.Error("Error retreiving list of transactions.")
|
||||
}
|
||||
if len(response.Transactions) == 0 {
|
||||
t.Error("Did not get any transactions.")
|
||||
}
|
||||
})
|
||||
}
|
11
http/http.go
11
http/http.go
@ -4,7 +4,6 @@ import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.javil.eu/jacob1123/budgeteer"
|
||||
"git.javil.eu/jacob1123/budgeteer/bcrypt"
|
||||
@ -25,11 +24,15 @@ const (
|
||||
expiration = 72
|
||||
)
|
||||
|
||||
// Serve starts the HTTP Server
|
||||
// Serve starts the http server
|
||||
func (h *Handler) Serve() {
|
||||
router := gin.Default()
|
||||
router.FuncMap["now"] = time.Now
|
||||
h.LoadRoutes(router)
|
||||
router.Run(":1323")
|
||||
}
|
||||
|
||||
// LoadRoutes initializes all the routes
|
||||
func (h *Handler) LoadRoutes(router *gin.Engine) {
|
||||
static, err := fs.Sub(web.Static, "dist")
|
||||
if err != nil {
|
||||
panic("couldn't open static files")
|
||||
@ -77,8 +80,6 @@ func (h *Handler) Serve() {
|
||||
transaction := authenticated.Group("/transaction")
|
||||
transaction.POST("/new", h.newTransaction)
|
||||
transaction.POST("/:transactionid", h.newTransaction)
|
||||
|
||||
router.Run(":1323")
|
||||
}
|
||||
|
||||
func enableCachingForStaticFiles() gin.HandlerFunc {
|
||||
|
@ -84,11 +84,13 @@ func (h *Handler) loginPost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, struct {
|
||||
Token string
|
||||
User postgres.User
|
||||
Budgets []postgres.Budget
|
||||
}{t, user, budgets})
|
||||
c.JSON(http.StatusOK, LoginResponse{t, user, budgets})
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Token string
|
||||
User postgres.User
|
||||
Budgets []postgres.Budget
|
||||
}
|
||||
|
||||
type registerInformation struct {
|
||||
@ -108,13 +110,13 @@ func (h *Handler) registerPost(c *gin.Context) {
|
||||
|
||||
_, err := h.Service.GetUserByUsername(c.Request.Context(), register.Email)
|
||||
if err == nil {
|
||||
c.AbortWithError(http.StatusUnauthorized, err)
|
||||
c.AbortWithError(http.StatusBadRequest, fmt.Errorf("email is already taken"))
|
||||
return
|
||||
}
|
||||
|
||||
hash, err := h.CredentialsVerifier.Hash(register.Password)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusUnauthorized, err)
|
||||
c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -140,9 +142,5 @@ func (h *Handler) registerPost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, struct {
|
||||
Token string
|
||||
User postgres.User
|
||||
Budgets []postgres.Budget
|
||||
}{t, user, budgets})
|
||||
c.JSON(http.StatusOK, LoginResponse{t, user, budgets})
|
||||
}
|
||||
|
@ -18,9 +18,8 @@ type Database struct {
|
||||
}
|
||||
|
||||
// Connect to a database
|
||||
func Connect(server string, user string, password string, database string) (*Database, error) {
|
||||
connString := fmt.Sprintf("postgres://%s:%s@%s/%s", user, password, server, database)
|
||||
conn, err := sql.Open("pgx", connString)
|
||||
func Connect(typ string, connString string) (*Database, error) {
|
||||
conn, err := sql.Open(typ, connString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open connection: %w", err)
|
||||
}
|
||||
|
@ -56,6 +56,12 @@ export default defineComponent({
|
||||
if(e.key == "Enter") {
|
||||
const selected = this.$data.Suggestions[0];
|
||||
this.selectElement(selected);
|
||||
const el = (<HTMLInputElement>e.target);
|
||||
const inputElements = Array.from(el.ownerDocument.querySelectorAll('input:not([disabled]):not([readonly])'));
|
||||
const currentIndex = inputElements.indexOf(el);
|
||||
const nextElement = inputElements[currentIndex < inputElements.length - 1 ? currentIndex + 1 : 0];
|
||||
(<HTMLInputElement>nextElement).focus();
|
||||
|
||||
}
|
||||
},
|
||||
selectElement(element : Suggestion) {
|
||||
@ -82,7 +88,7 @@ export default defineComponent({
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<input @keypress="keypress" v-if="Selected == undefined" v-model="SearchQuery" />
|
||||
<input class="border-b-2 border-black" @keypress="keypress" v-if="Selected == undefined" v-model="SearchQuery" />
|
||||
<span @click="clear" v-if="Selected != undefined" class="bg-gray-300">{{Selected.Name}}</span>
|
||||
<div v-if="Suggestions.length > 0" class="absolute bg-gray-400 w-64 p-2">
|
||||
<span v-for="suggestion in Suggestions" class="block" @click="select" :value="suggestion.ID">
|
||||
|
@ -57,7 +57,7 @@ export default defineComponent({
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 90px;" class="text-sm">
|
||||
<input type="date" v-model="TransactionDate" />
|
||||
<input class="border-b-2 border-black" type="date" v-model="TransactionDate" />
|
||||
</td>
|
||||
<td style="max-width: 150px;">
|
||||
<Autocomplete v-model="Payee" type="payees" />
|
||||
@ -66,10 +66,10 @@ export default defineComponent({
|
||||
<Autocomplete v-model="Category" type="categories" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" v-model="Memo" />
|
||||
<input class="block w-full border-b-2 border-black" type="text" v-model="Memo" />
|
||||
</td>
|
||||
<td style="width: 80px;" class="text-right">
|
||||
<input class="text-right" type="currency" v-model="Amount" />
|
||||
<input class="text-right block w-full border-b-2 border-black" type="currency" v-model="Amount" />
|
||||
</td>
|
||||
<td style="width: 20px;">
|
||||
<input type="submit" @click="saveTransaction" value="Save" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user