Reorganize project structure and fix SQLite usage

This commit is contained in:
bronku 2025-03-23 10:45:42 +01:00
parent 0e762841a3
commit e2dda0ade7
20 changed files with 76 additions and 46 deletions

78
internal/auth/auth.go Normal file
View file

@ -0,0 +1,78 @@
package auth
import (
_ "embed"
"fmt"
"net/http"
"time"
)
//go:embed login.html
var loginPage string
//go:embed wrongPassword.html
var wrongPassword string
type Authenticator struct {
sessions map[string]token
}
func New() *Authenticator {
return &Authenticator{sessions: make(map[string]token)}
}
func (a *Authenticator) login(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.Header().Set("content-type", "text/html")
fmt.Fprint(w, loginPage)
return
}
err := r.ParseForm()
if err != nil {
w.Header().Set("content-type", "text/html")
fmt.Fprint(w, loginPage)
return
}
login := r.PostFormValue("login")
password := r.PostFormValue("password")
if login != "admin" || password != "secret" {
w.Header().Set("content-type", "text/html")
fmt.Fprint(w, wrongPassword)
return
}
key := generateKey()
a.sessions[key] = token{userName: login, created: time.Now(), lastAccess: time.Now()}
c := http.Cookie{
Name: "token",
Value: key,
HttpOnly: true,
// #todo (in prod) uncomment this line
//Secure: true,
SameSite: http.SameSiteStrictMode,
Path: "/",
}
http.SetCookie(w, &c)
http.Redirect(w, r, "/", http.StatusFound)
}
func (a *Authenticator) Middleware(in http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.String() == "/login" {
a.login(w, r)
return
}
c, err := r.Cookie("token")
if err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
value, ok := a.sessions[c.Value]
if !ok || time.Since(value.lastAccess) > time.Hour*24 || time.Since(value.lastAccess) > time.Hour*240 {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
value.lastAccess = time.Now()
a.sessions[c.Value] = value
in.ServeHTTP(w, r)
})
}

12
internal/auth/login.html Normal file
View file

@ -0,0 +1,12 @@
<header>
<h1>Login</h1>
</header>
<main>
<form method="post">
<label>Login</label>
<input type="text" name="login" />
<label>Password</label>
<input type="password" name="password" />
<button type="submit">login</button>
</form>
</main>

19
internal/auth/token.go Normal file
View file

@ -0,0 +1,19 @@
package auth
import (
"crypto/rand"
"encoding/base64"
"time"
)
type token struct {
userName string
created time.Time
lastAccess time.Time
}
func generateKey() string {
key := [32]byte{}
rand.Read(key[:])
return base64.StdEncoding.EncodeToString(key[:])
}

View file

@ -0,0 +1,12 @@
<header>
<h1>Wrong Password</h1>
</header>
<main>
<form method="post">
<label>Login</label>
<input type="text" name="login" />
<label>Password</label>
<input type="password" name="password" />
<button type="submit">login</button>
</form>
</main>

View file

@ -0,0 +1,16 @@
package logging
import (
"fmt"
"net/http"
"time"
)
func Middleware(in http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
fmt.Println(r.Method, r.URL.String())
in.ServeHTTP(w, r)
fmt.Println(r.Method, "finished in", time.Since(start))
})
}

13
internal/server/errors.go Normal file
View file

@ -0,0 +1,13 @@
package server
import (
"fmt"
"net/http"
)
func errorPage(err error, status int) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(status)
fmt.Fprint(w, err.Error())
}
}

View file

@ -0,0 +1,5 @@
package server
import "net/http"
type fetcher func(r *http.Request) (any, int, error)

45
internal/server/get.go Normal file
View file

@ -0,0 +1,45 @@
package server
import (
"net/http"
"strconv"
"strings"
"github.com/Bronku/iroon/internal/store"
)
func (h *Server) index(r *http.Request) (any, int, error) {
data, err := h.s.GetOrders()
return data, http.StatusOK, err
}
func (h *Server) getOrder(r *http.Request) (any, int, error) {
type formData struct {
Order store.Order
Catalogue []store.Cake
}
var err error
var data formData
data.Catalogue, err = h.s.GetCakes()
if err != nil {
return nil, http.StatusInternalServerError, err
}
url := strings.Split(r.URL.String(), "/")
if len(url) < 3 || url[2] == "" {
return data, http.StatusOK, nil
}
id, err := strconv.Atoi(url[2])
if err != nil {
return nil, http.StatusBadRequest, err
}
data.Order, err = h.s.GetOrder(id)
if err != nil {
return nil, http.StatusNotFound, err
}
return data, http.StatusOK, nil
}

56
internal/server/post.go Normal file
View file

@ -0,0 +1,56 @@
package server
import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/Bronku/iroon/internal/store"
)
func (h *Server) postOrder(r *http.Request) (any, int, error) {
cakes, err := h.s.GetCakes()
if err != nil {
return nil, http.StatusInternalServerError, err
}
err = r.ParseForm()
if err != nil {
return nil, http.StatusBadRequest, err
}
var n store.Order
n.ID, err = strconv.Atoi(r.FormValue("id"))
if err != nil {
return nil, http.StatusBadRequest, err
}
n.Paid, err = strconv.Atoi(r.FormValue("paid"))
if err != nil {
return nil, http.StatusBadRequest, err
}
n.Date, err = time.Parse("2006-01-02", r.FormValue("date"))
if err != nil {
return nil, http.StatusBadRequest, err
}
n.Cakes = make([]store.Cake, 0)
for _, e := range cakes {
e.Amount, err = strconv.Atoi(r.FormValue(fmt.Sprintf("cake[%d]", e.ID)))
if err != nil {
continue
}
n.Cakes = append(n.Cakes, e)
}
n.Accepted = time.Now()
n.Name = strings.TrimSpace(r.FormValue("name"))
n.Surname = strings.TrimSpace(r.FormValue("surname"))
n.Phone = strings.TrimSpace(r.FormValue("phone"))
n.Location = strings.TrimSpace(r.FormValue("location"))
n.Status = strings.TrimSpace(r.FormValue("status"))
n.ID, err = h.s.SaveOrder(n)
return n, http.StatusAccepted, err
}

58
internal/server/server.go Normal file
View file

@ -0,0 +1,58 @@
package server
import (
"html/template"
"net/http"
"github.com/Bronku/iroon/internal/store"
)
type Server struct {
tmpl *template.Template
s *store.Store
http.Handler
}
func (h *Server) Close() {
if h.s != nil {
h.s.Close()
}
}
func (h *Server) openStore() error {
var err error
h.s, err = store.OpenStore("./foo.db")
if err != nil {
h.s.Close()
}
return err
}
func (h *Server) loadHandler() {
mux := http.NewServeMux()
mux.HandleFunc("GET /order/", h.render(h.getOrder, "order.html"))
mux.HandleFunc("GET /", h.render(h.index, "index.html"))
mux.HandleFunc("POST /order/", h.render(h.postOrder, "confirmation.html"))
h.Handler = mux
}
func New() (*Server, error) {
var server Server
var err error
err = server.loadTemplates()
if err != nil {
return nil, err
}
err = server.openStore()
if err != nil {
return nil, err
}
server.loadHandler()
return &server, nil
}

View file

@ -0,0 +1,31 @@
package server
import (
"embed"
"html/template"
"net/http"
)
//go:embed templates/*
var templates embed.FS
func (h *Server) loadTemplates() error {
var err error
h.tmpl, err = template.ParseFS(templates, "templates/*")
return err
}
func (s *Server) render(fetch fetcher, templateName string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "text/html")
data, code, err := fetch(r)
if err != nil {
errorPage(err, code).ServeHTTP(w, r)
return
}
err = s.tmpl.ExecuteTemplate(w, templateName, data)
if err != nil {
errorPage(err, http.StatusInternalServerError).ServeHTTP(w, r)
}
}
}

View file

@ -0,0 +1,9 @@
<header>
<h1>Confirmation</h1>
</header>
<nav>
<a href="/">index</a>
</nav>
<main>
<h2>Order {{.ID}} for {{.Name}} {{.Surname}} confirmed</h2>
</main>

View file

@ -0,0 +1,36 @@
<header>
<h1>Kill me please</h1>
</header>
<nav>
<a href="order">New</a>
</nav>
<main>
<table>
<tr>
<th></th>
<th>Name</th>
<th>Surname</th>
<th>Phone</th>
<th>Location</th>
<th>Date</th>
<th>Accepted</th>
<th>Status</th>
<th>Paid</th>
<th>Cakes</th>
</tr>
{{range .}}
<tr>
<th><a href="/order/{{.ID}}">edit</a></th>
<th>{{.Name}}</th>
<th>{{.Surname}}</th>
<th>{{.Phone}}</th>
<th>{{.Location}}</th>
<th>{{.Date.Format "2006-01-02"}}</th>
<th>{{.Accepted.Format "2006-01-02"}}</th>
<th>{{.Status}}</th>
<th>{{.Paid}}</th>
<th>{{range .Cakes}} {{.Name}} {{.Amount}} {{end}}</th>
</tr>
{{end}}
</table>
</main>

View file

@ -0,0 +1,91 @@
<header>
<h1>New Order</h1>
</header>
<nav>
<a href="/">back</a>
</nav>
<main>
<form method="post">
{{with .Order}}
<div>
<h2>Info</h2>
<input hidden="true" name="id" value="{{.ID}}">
<label>name</label>
<input type="text" name="name" value="{{.Name}}">
<label>surname</label>
<input type="text" name="surname" value="{{.Surname}}">
<label>phone</label>
<input type="tel" name="phone" value="{{.Phone}}">
<label>location</label>
<select name="location">
<option {{ if eq .Location "Kartuzy" }}selected{{ end }} value="Kartuzy">Kartuzy</option>
<option {{ if eq .Location "Somonino" }}selected{{ end }} value="Somonino">Somonino</option>
</select>
<label>date</label>
<input type="date" name="date" value="{{.Date.Format "2006-01-02"}}">
<label>status</label>
<select name="status" id="status" >
<option {{ if eq .Status "accepted" }}selected{{ end }} value="accepted">accepted</option>
<option {{ if eq .Status "done" }}selected{{ end }} value="done">done</option>
</select>
<label>paid</label>
<input type="number" name="paid" min="0" value="{{.Paid}}">
</div>
<div>
<h2>Basket</h2>
<ul id="basket">
</ul>
</div>
{{end}}
{{with .Catalogue}}
<div>
<h2>Catalogue</h2>
<ul>
{{range .}}
<li>
{{.Name}} {{.Price}} <button type=button onClick="addCake({{.ID}},'{{.Name}} {{.Price}}', 1)">add</button>
</li>
{{end}}
</ul>
</div>
{{end}}
<button type="submit">submit</button>
</form>
</main>
<script>
function addCake(id, name, amount){
var cake = document.getElementById("cake["+id+"]")
if (cake != null){
var value = Number(cake.getAttribute("value"))
cake.setAttribute("value", value+1)
return;
}
var label = document.createElement("label")
label.innerText = name
var input = document.createElement("input")
input.setAttribute("id","cake["+id+"]")
input.setAttribute("type", "number")
input.setAttribute("min", "0")
input.setAttribute("name", "cake["+id+"]")
input.setAttribute("value", amount)
var button = document.createElement("button")
button.setAttribute("type", "button")
button.setAttribute("onClick", "removeCake("+id+")")
button.innerText= "delete"
var li = document.createElement("li")
li.appendChild(label)
li.appendChild(input)
li.appendChild(button)
li.setAttribute("id", "li["+id+"]")
var ul = document.getElementById("basket")
ul.appendChild(li)
}
function removeCake(id){
document.getElementById("li["+id+"]").remove()
}
{{range .Order.Cakes}}
addCake({{.ID}}, '{{.Name}} {{.Price}}', {{.Amount}})
{{end}}
</script>

23
internal/store/model.go Normal file
View file

@ -0,0 +1,23 @@
package store
import "time"
type Cake struct {
Name string
ID int
Price int
Amount int
}
type Order struct {
ID int
Name string
Surname string
Phone string
Location string
Date time.Time
Accepted time.Time
Status string
Paid int // increments of 0.01
Cakes []Cake
}

26
internal/store/schema.sql Normal file
View file

@ -0,0 +1,26 @@
create table cake (
id integer primary key autoincrement,
name text not null unique,
price integer not null
);
create table customer_order (
id integer primary key autoincrement,
name text,
surname text,
phone text,
location text,
order_date string not null,
delivery_date string not null,
status text,
paid integer
);
create table ordered_cake (
customer_order integer references customer_order (id) not null,
cake integer references cake (id) not null,
amount integer not null,
primary key (customer_order, cake)
);
PRAGMA user_version = 1;

252
internal/store/store.go Normal file
View file

@ -0,0 +1,252 @@
package store
import (
"database/sql"
_ "embed"
"errors"
"strconv"
"time"
_ "github.com/mattn/go-sqlite3"
)
//go:embed schema.sql
var schema string
type Store struct {
db *sql.DB
}
func OpenStore(filename string) (*Store, error) {
var out Store
var err error
out.db, err = sql.Open("sqlite3", filename)
if err != nil {
return &out, err
}
err = out.loadSchema()
return &out, err
}
func (s *Store) Close() {
if s.db != nil {
s.db.Close()
}
}
func (s *Store) version() int {
out := -1
row, err := s.db.Query("PRAGMA user_version;")
if err != nil {
return out
}
defer row.Close()
row.Next()
_ = row.Scan(&out)
return out
}
func (s *Store) loadSchema() error {
if s.db == nil {
return errors.New("database doesn't exist")
}
if s.version() == 1 {
return nil
}
_, err := s.db.Exec(schema)
return err
}
func (s *Store) GetCake(id int) (Cake, error) {
out := Cake{ID: id}
row, err := s.db.Query("select name, price from cake where id = ?;", id)
if err != nil {
return out, err
}
defer row.Close()
row.Next()
err = row.Scan(&out.Name, &out.Price)
return out, err
}
func (s *Store) GetCakes() ([]Cake, error) {
rows, err := s.db.Query("select id, name, price from cake")
if err != nil {
return nil, err
}
defer rows.Close()
cakes := make([]Cake, 0)
for rows.Next() {
var c Cake
c.Amount = 0
err = rows.Scan(&c.ID, &c.Name, &c.Price)
if err != nil {
return nil, err
}
cakes = append(cakes, c)
}
return cakes, err
}
func (s *Store) SaveCake(newCake Cake) (int, error) {
query := "insert into cake(name, price) values (?, ?) returning id;"
if newCake.ID != 0 {
query = "update cake set name = ? , price = ? where id = "
query += strconv.Itoa(newCake.ID) + " returning id;"
}
row, err := s.db.Query(query, newCake.Name, newCake.Price)
if err != nil {
return 0, err
}
defer row.Close()
if !row.Next() {
return 0, errors.New("The database didn't respond with an id")
}
err = row.Scan(&newCake.ID)
return newCake.ID, err
}
func (s *Store) GetOrder(id int) (Order, error) {
var out Order
row, err := s.db.Query("select id, name, surname, phone, location, order_date, delivery_date, status, paid from customer_order where id = ?;", id)
if err != nil {
return out, err
}
defer row.Close()
var order_date, delivery_date string
row.Next()
err = row.Scan(&out.ID, &out.Name, &out.Surname, &out.Phone, &out.Location, &order_date, &delivery_date, &out.Status, &out.Paid)
if err != nil {
return out, err
}
out.Accepted, _ = time.Parse("2006-01-02 15:04", order_date)
out.Date, _ = time.Parse("2006-01-02 15:04", delivery_date)
out.Cakes = make([]Cake, 0)
rows, err := s.db.Query("select cake, amount from ordered_cake where customer_order = ?;", id)
if err != nil {
return out, err
}
defer rows.Close()
for rows.Next() {
var newCake Cake
err = rows.Scan(&newCake.ID, &newCake.Amount)
if err != nil {
return out, err
}
cakeData, err := s.GetCake(newCake.ID)
if err != nil {
continue
}
newCake.Name = cakeData.Name
newCake.Price = cakeData.Price
out.Cakes = append(out.Cakes, newCake)
}
return out, nil
}
func (s *Store) GetOrders() ([]Order, error) {
var out []Order
rows, err := s.db.Query("select id, name, surname, phone, location, order_date, delivery_date, status, paid from customer_order;")
if err != nil {
return out, err
}
defer rows.Close()
for rows.Next() {
var o Order
var order_date, delivery_date string
err = rows.Scan(&o.ID, &o.Name, &o.Surname, &o.Phone, &o.Location, &order_date, &delivery_date, &o.Status, &o.Paid)
if err != nil {
return nil, err
}
o.Accepted, _ = time.Parse("2006-01-02 15:04", order_date)
o.Date, _ = time.Parse("2006-01-02 15:04", delivery_date)
o.Cakes = make([]Cake, 0)
rows, err := s.db.Query("select cake, amount from ordered_cake where customer_order = ?;", o.ID)
if err != nil {
return out, err
}
defer rows.Close()
for rows.Next() {
var newCake Cake
err = rows.Scan(&newCake.ID, &newCake.Amount)
if err != nil {
return out, err
}
cakeData, err := s.GetCake(newCake.ID)
if err != nil {
continue
}
newCake.Name = cakeData.Name
newCake.Price = cakeData.Price
o.Cakes = append(o.Cakes, newCake)
}
out = append(out, o)
}
return out, nil
}
func (s *Store) SaveOrder(newOrder Order) (int, error) {
query := "insert into customer_order(name, surname, phone, location, order_date, delivery_date, status, paid) values (?, ?, ?, ?, ?, ?, ?, ?) returning id;"
if newOrder.ID != 0 {
query = "update customer_order set name = ?, surname = ?, phone = ?, location = ?, order_date = ?, delivery_date = ?, status = ?, paid = ? where id = "
query += strconv.Itoa(newOrder.ID) + " returning id;"
}
tx, err := s.db.Begin()
if err != nil {
return 0, err
}
accepted := newOrder.Accepted.Format("2006-01-02 15:04")
date := newOrder.Date.Format("2006-01-02 15:04")
row, err := tx.Query(query, newOrder.Name, newOrder.Surname, newOrder.Phone, newOrder.Location, accepted, date, newOrder.Status, newOrder.Paid)
if err != nil {
_ = tx.Rollback()
return 0, err
}
defer row.Close()
if !row.Next() {
_ = tx.Rollback()
return 0, errors.New("The database didn't respond with an id")
}
err = row.Scan(&newOrder.ID)
if err != nil {
_ = tx.Rollback()
return newOrder.ID, err
}
// remove all ordered_cakes associated with this order before inserting
query = "delete from ordered_cake where customer_order = ?;"
_, err = tx.Exec(query, newOrder.ID)
if err != nil {
_ = tx.Rollback()
return newOrder.ID, err
}
// add all ordered_cakes for this order
query = "insert into ordered_cake(customer_order, cake, amount) values (?,?,?);"
for _, e := range newOrder.Cakes {
_, err := tx.Exec(query, newOrder.ID, e.ID, e.Amount)
if err != nil {
_ = tx.Rollback()
return newOrder.ID, err
}
}
err = tx.Commit()
return newOrder.ID, err
}

View file

@ -0,0 +1,294 @@
package store
import (
"testing"
"time"
)
func TestStore(t *testing.T) {
s, err := OpenStore("file:memdb1?mode=memory&cache=shared")
if err != nil {
t.Fatalf("Failed to create store: %v", err)
}
defer s.Close()
// ensure db is empty
cakes, err := s.GetCakes()
if err != nil {
t.Errorf("Error getting cakes: %v", err)
}
if len(cakes) != 0 {
t.Errorf("Expected empty cakes list, got: %v", cakes)
}
orders, err := s.GetOrders()
if err != nil {
t.Errorf("Error getting orders: %v", err)
}
if len(orders) != 0 {
t.Errorf("Expected empty orders list, got: %v", orders)
}
// create new cake
newCake := Cake{Name: "Chocolate Cake", Price: 2500}
newCake.ID, err = s.SaveCake(newCake)
if err != nil {
t.Fatalf("Failed to save new cake: %v", err)
}
if newCake.ID <= 0 {
t.Errorf("Expected positive cake ID, got: %d", newCake.ID)
}
// create another cake
anotherCake := Cake{Name: "Another Cake", Price: 3000}
anotherCake.ID, err = s.SaveCake(anotherCake)
if err != nil {
t.Fatalf("Failed to save new cake: %v", err)
}
if anotherCake.ID <= 0 {
t.Errorf("Expected positive cake ID, got: %d", anotherCake.ID)
}
// Update Existing Cake
newCake.Name = "Updated Cake"
newCake.Price = 100
newID, err := s.SaveCake(newCake)
if newID != newCake.ID {
t.Error("wrong id")
}
if err != nil {
t.Error("error updating cake", err)
}
// Update Non-existing Cake
var updatedCake Cake
updatedCake.ID = 10
_, err = s.SaveCake(updatedCake)
if err == nil {
t.Error("did not return an error when attempted to update non existant cake")
}
// get cakes
cakes, err = s.GetCakes()
if err != nil {
t.Fatalf("Failed to get all cakes after creation: %v", err)
}
if len(cakes) != 2 {
t.Error("Expected two cakes returned, got ", len(cakes))
}
for i, c := range cakes {
if c.ID == newCake.ID {
newCake.Amount = i
}
if c.ID == anotherCake.ID {
anotherCake.Amount = i
}
}
if newCake.Amount == -1 {
t.Error("not found newCake")
}
if cakes[newCake.Amount].ID != newCake.ID || cakes[newCake.Amount].Name != newCake.Name || cakes[newCake.Amount].Price != newCake.Price {
t.Errorf("Want %v\nGot %v", newCake, cakes)
}
if anotherCake.Amount == -1 {
t.Error("not found anotherCake")
}
if cakes[anotherCake.Amount].ID != anotherCake.ID || cakes[anotherCake.Amount].Name != anotherCake.Name || cakes[anotherCake.Amount].Price != anotherCake.Price {
t.Errorf("Want %v\nGot %v", anotherCake, cakes)
}
selectedCake, err := s.GetCake(newCake.ID)
if err != nil {
t.Error("error getting a cake", err)
}
if selectedCake.ID != newCake.ID || selectedCake.Name != newCake.Name || selectedCake.Price != newCake.Price {
t.Errorf("Want %v\nGot %v", newCake, selectedCake)
}
selectedCake, err = s.GetCake(-1)
if err == nil {
t.Error("no error getting a non-existant cake")
}
// create new order
now := time.Now()
newOrder := Order{
Name: "John",
Surname: "Doe",
Phone: "123-456-7890",
Location: "Some Location",
Accepted: now,
Date: now.Add(time.Hour * 24),
Status: "Pending",
Paid: 1000,
Cakes: []Cake{{ID: newCake.ID, Amount: 2}, {ID: anotherCake.ID, Amount: 10}},
}
newOrder.ID, err = s.SaveOrder(newOrder)
if err != nil {
t.Fatalf("Failed to save new order: %v", err)
}
if newOrder.ID <= 0 {
t.Errorf("Expected positive order ID, got: %d", newOrder.ID)
}
// create another order
anotherOrder := Order{
Name: "Jane",
Surname: "Doe",
Phone: "123-456-7890",
Location: "New Location",
Accepted: now,
Date: now.Add(time.Hour * 192),
Status: "Accepted",
Paid: 1500,
Cakes: []Cake{{ID: newCake.ID, Amount: 100}},
}
anotherOrder.ID, err = s.SaveOrder(anotherOrder)
if err != nil {
t.Fatalf("Failed to save new order: %v", err)
}
if anotherOrder.ID <= 0 {
t.Errorf("Expected positive order ID, got: %d", anotherOrder.ID)
}
// update existing order
newOrder.Name = "James"
newOrder.Status = "Done"
newOrder.Cakes = newOrder.Cakes[1:]
newID, err = s.SaveOrder(newOrder)
if newID != newOrder.ID {
t.Error("wrong id")
}
if err != nil {
t.Error("error updating order", err)
}
// update non existing order
var updatedOrder Order
updatedOrder.ID = 10
_, err = s.SaveOrder(updatedOrder)
if err == nil {
t.Error("did not return an error when attempted to update non existant cake")
}
// get orders
orders, err = s.GetOrders()
if err != nil {
t.Fatalf("Failed to get all orders after creation: %v", err)
}
if len(orders) != 2 {
t.Error("Expected two orders returned, got ", len(orders))
}
newOrderPos := -1
anotherOrderPos := -1
for i, o := range orders {
if o.ID == newOrder.ID {
newOrderPos = i
}
if o.ID == anotherOrder.ID {
anotherOrderPos = i
}
}
if newOrderPos == -1 {
t.Error("not found newOrder")
}
if newOrder.Name != orders[newOrderPos].Name ||
newOrder.Surname != orders[newOrderPos].Surname ||
newOrder.Phone != orders[newOrderPos].Phone ||
newOrder.Status != orders[newOrderPos].Status ||
newOrder.Location != orders[newOrderPos].Location ||
newOrder.Accepted.Format("2006-01-02 15:04") != orders[newOrderPos].Accepted.Format("2006-01-02 15:04") ||
newOrder.Date.Format("2006-01-02 15:04") != orders[newOrderPos].Date.Format("2006-01-02 15:04") {
t.Errorf("Want %v\nGot %v", newOrder, orders[newOrderPos])
}
if !areCakeSlicesEqual(newOrder.Cakes, orders[newOrderPos].Cakes) {
t.Errorf("Want %v\nGot %v", newOrder.Cakes, orders[newOrderPos].Cakes)
}
if anotherOrderPos == -1 {
t.Error("not found anotherOrder")
}
if anotherOrder.Name != orders[anotherOrderPos].Name ||
anotherOrder.Surname != orders[anotherOrderPos].Surname ||
anotherOrder.Phone != orders[anotherOrderPos].Phone ||
anotherOrder.Status != orders[anotherOrderPos].Status ||
anotherOrder.Location != orders[anotherOrderPos].Location ||
anotherOrder.Accepted.Format("2006-01-02 15:04") != orders[anotherOrderPos].Accepted.Format("2006-01-02 15:04") ||
anotherOrder.Date.Format("2006-01-02 15:04") != orders[anotherOrderPos].Date.Format("2006-01-02 15:04") {
t.Errorf("Want %v\nGot %v", anotherOrder, orders[anotherOrderPos])
}
if !areCakeSlicesEqual(anotherOrder.Cakes, orders[anotherOrderPos].Cakes) {
t.Errorf("Want %v\nGot %v", anotherOrder.Cakes, orders[anotherOrderPos].Cakes)
}
selectedOrder, err := s.GetOrder(newOrder.ID)
if err != nil {
t.Error("error getting an order by id ", err)
}
if newOrder.Name != selectedOrder.Name ||
newOrder.Surname != selectedOrder.Surname ||
newOrder.Phone != selectedOrder.Phone ||
newOrder.Status != selectedOrder.Status ||
newOrder.Location != selectedOrder.Location ||
newOrder.Accepted.Format("2006-01-02 15:04") != selectedOrder.Accepted.Format("2006-01-02 15:04") ||
newOrder.Date.Format("2006-01-02 15:04") != selectedOrder.Date.Format("2006-01-02 15:04") {
t.Errorf("Want %v\nGot %v", newOrder, selectedOrder)
}
if !areCakeSlicesEqual(newOrder.Cakes, selectedOrder.Cakes) {
t.Errorf("Want %v\nGot %v", newOrder.Cakes, selectedOrder.Cakes)
}
_, err = s.GetOrder(-1)
if err == nil {
t.Error("no error getting invalid order")
}
}
func TestSliceComparison(t *testing.T) {
a := []Cake{
{Name: "ok", Price: 100, ID: 12, Amount: 10},
{Name: "ok", Price: 11, ID: 13, Amount: 1},
{Name: "ok", Price: 100, ID: 10, Amount: 11},
}
b := []Cake{
{Name: "okas", Price: 110, ID: 12, Amount: 10},
{Name: "oksa", Price: 11, ID: 13, Amount: 1},
{Name: "oasdk", Price: 101, ID: 10, Amount: 11},
}
if !areCakeSlicesEqual(a, b) {
t.Error("aren't equal")
}
}
func areCakesEqual(a, b Cake) bool {
return a.ID == b.ID && a.Amount == b.Amount
}
func areCakeSlicesEqual(a, b []Cake) bool {
if len(a) != len(b) {
return false
}
matched := make([]bool, len(a))
mainLoop:
for _, e1 := range a {
for i, e2 := range b {
if matched[i] {
continue
}
if areCakesEqual(e1, e2) {
matched[i] = true
continue mainLoop
}
}
}
for _, e := range matched {
if e == false {
return false
}
}
return true
}