[TASK] add nessasary attributes + timeout on locked goods
This commit is contained in:
parent
84f060cb3b
commit
9b10999cde
|
@ -33,7 +33,8 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log.Panic(err)
|
log.Log.Panic(err)
|
||||||
}
|
}
|
||||||
|
grw := models.NewGoodReleaseWorker(config.GoodRelease)
|
||||||
|
go grw.Start()
|
||||||
// Startwebsrver
|
// Startwebsrver
|
||||||
router := goji.NewMux()
|
router := goji.NewMux()
|
||||||
web.BindAPI(router)
|
web.BindAPI(router)
|
||||||
|
@ -50,6 +51,7 @@ func main() {
|
||||||
|
|
||||||
// Stop services
|
// Stop services
|
||||||
srv.Close()
|
srv.Close()
|
||||||
|
grw.Close()
|
||||||
database.Close()
|
database.Close()
|
||||||
|
|
||||||
log.Log.Info("received", sig)
|
log.Log.Info("received", sig)
|
||||||
|
|
|
@ -6,3 +6,7 @@ type = "sqlite3"
|
||||||
connection = "file::memory:?mode=memory&cache=shared"
|
connection = "file::memory:?mode=memory&cache=shared"
|
||||||
# For Master-Slave cluster
|
# For Master-Slave cluster
|
||||||
# read_connection = ""
|
# read_connection = ""
|
||||||
|
|
||||||
|
[good_release]
|
||||||
|
timer = "5m"
|
||||||
|
after = "30m"
|
||||||
|
|
|
@ -11,11 +11,11 @@ import (
|
||||||
|
|
||||||
func status(w http.ResponseWriter, r *http.Request) {
|
func status(w http.ResponseWriter, r *http.Request) {
|
||||||
log := logger.HTTP(r)
|
log := logger.HTTP(r)
|
||||||
var good []*models.Good
|
var goods []*models.Good
|
||||||
var count int64
|
var count int64
|
||||||
var avg float64
|
var avg float64
|
||||||
database.Read.Find(&good).Count(&count) //.Avg(&avg)
|
database.Read.Find(&goods).Count(&count)
|
||||||
database.Read.Raw("SELECT avg(g.gcount) as avg FROM (select count(*) as gcount FROM good g GROUP BY g.product_id) g").Row().Scan(&avg)
|
database.Read.Raw("SELECT avg(g.gcount) as avg FROM (select count(*) as gcount FROM good g WHERE deleted_at is NULL GROUP BY g.product_id) g").Row().Scan(&avg)
|
||||||
lib.Write(w, map[string]interface{}{
|
lib.Write(w, map[string]interface{}{
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"database": map[string]interface{}{
|
"database": map[string]interface{}{
|
||||||
|
|
|
@ -17,15 +17,15 @@ func TestStatus(t *testing.T) {
|
||||||
|
|
||||||
database.Write.Create(&models.Good{
|
database.Write.Create(&models.Good{
|
||||||
ProductID: 3,
|
ProductID: 3,
|
||||||
Comment: "regal 1",
|
Position: "regal 1",
|
||||||
})
|
})
|
||||||
database.Write.Create(&models.Good{
|
database.Write.Create(&models.Good{
|
||||||
ProductID: 3,
|
ProductID: 3,
|
||||||
Comment: "regal 2",
|
Position: "regal 2",
|
||||||
})
|
})
|
||||||
database.Write.Create(&models.Good{
|
database.Write.Create(&models.Good{
|
||||||
ProductID: 1,
|
ProductID: 1,
|
||||||
Comment: "regal 10",
|
Position: "regal 10",
|
||||||
})
|
})
|
||||||
|
|
||||||
r, w := session.JSONRequest("GET", "/api/status", nil)
|
r, w := session.JSONRequest("GET", "/api/status", nil)
|
||||||
|
|
|
@ -26,17 +26,20 @@ func Open(c Config) (err error) {
|
||||||
writeLog := log.Log.WithField("db", "write")
|
writeLog := log.Log.WithField("db", "write")
|
||||||
config = &c
|
config = &c
|
||||||
Write, err = gorm.Open(config.Type, config.Connection)
|
Write, err = gorm.Open(config.Type, config.Connection)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
Write.SingularTable(true)
|
Write.SingularTable(true)
|
||||||
Write.LogMode(c.Logging)
|
Write.LogMode(c.Logging)
|
||||||
Write.SetLogger(writeLog)
|
Write.SetLogger(writeLog)
|
||||||
Write.Callback().Create().Remove("gorm:update_time_stamp")
|
Write.Callback().Create().Remove("gorm:update_time_stamp")
|
||||||
Write.Callback().Update().Remove("gorm:update_time_stamp")
|
Write.Callback().Update().Remove("gorm:update_time_stamp")
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(config.ReadConnection) > 0 {
|
if len(config.ReadConnection) > 0 {
|
||||||
readLog := log.Log.WithField("db", "read")
|
readLog := log.Log.WithField("db", "read")
|
||||||
Read, err = gorm.Open(config.Type, config.ReadConnection)
|
Read, err = gorm.Open(config.Type, config.ReadConnection)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
Read.SingularTable(true)
|
Read.SingularTable(true)
|
||||||
Read.LogMode(c.Logging)
|
Read.LogMode(c.Logging)
|
||||||
Read.SetLogger(readLog)
|
Read.SetLogger(readLog)
|
||||||
|
@ -52,7 +55,6 @@ func Open(c Config) (err error) {
|
||||||
func Close() {
|
func Close() {
|
||||||
Write.Close()
|
Write.Close()
|
||||||
Write = nil
|
Write = nil
|
||||||
|
|
||||||
if len(config.ReadConnection) > 0 {
|
if len(config.ReadConnection) > 0 {
|
||||||
Read.Close()
|
Read.Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,17 @@ func TestOpenOneDB(t *testing.T) {
|
||||||
func TestOpenTwoDB(t *testing.T) {
|
func TestOpenTwoDB(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
AddModel(&TestModel{})
|
AddModel(&TestModel{})
|
||||||
|
|
||||||
c := Config{
|
c := Config{
|
||||||
|
Type: "sqlite3",
|
||||||
|
Logging: true,
|
||||||
|
Connection: "file:database?mode=memory",
|
||||||
|
ReadConnection: "file/",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := Open(c)
|
||||||
|
assert.Error(err, "no error found")
|
||||||
|
|
||||||
|
c = Config{
|
||||||
Type: "sqlite3",
|
Type: "sqlite3",
|
||||||
Logging: true,
|
Logging: true,
|
||||||
Connection: "file:database?mode=memory",
|
Connection: "file:database?mode=memory",
|
||||||
|
@ -54,7 +63,7 @@ func TestOpenTwoDB(t *testing.T) {
|
||||||
}
|
}
|
||||||
var count int64
|
var count int64
|
||||||
|
|
||||||
err := Open(c)
|
err = Open(c)
|
||||||
assert.NoError(err, "no error")
|
assert.NoError(err, "no error")
|
||||||
|
|
||||||
Write.Create(&TestModel{Value: "first"})
|
Write.Create(&TestModel{Value: "first"})
|
||||||
|
@ -67,5 +76,4 @@ func TestOpenTwoDB(t *testing.T) {
|
||||||
result := Read.Find(&list)
|
result := Read.Find(&list)
|
||||||
assert.Error(result.Error, "error, because it is the wrong database")
|
assert.Error(result.Error, "error, because it is the wrong database")
|
||||||
Close()
|
Close()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package models
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/influxdata/toml"
|
"github.com/BurntSushi/toml"
|
||||||
|
|
||||||
"github.com/genofire/hs_master-kss-monolith/lib/database"
|
"github.com/genofire/hs_master-kss-monolith/lib/database"
|
||||||
"github.com/genofire/hs_master-kss-monolith/lib/log"
|
"github.com/genofire/hs_master-kss-monolith/lib/log"
|
||||||
|
@ -11,8 +11,12 @@ import (
|
||||||
|
|
||||||
//Config the config File of this daemon
|
//Config the config File of this daemon
|
||||||
type Config struct {
|
type Config struct {
|
||||||
WebserverBind string
|
WebserverBind string `toml:"webserver_bind"`
|
||||||
Database database.Config
|
Database database.Config `toml:"database"`
|
||||||
|
GoodRelease struct {
|
||||||
|
After Duration `toml:"after"`
|
||||||
|
Timer Duration `toml:"timer"`
|
||||||
|
} `toml:"good_release"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadConfigFile reads a config model from path of a yml file
|
// ReadConfigFile reads a config model from path of a yml file
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Duration is a TOML datatype
|
||||||
|
// A duration string is a possibly signed sequence of
|
||||||
|
// decimal numbers and a unit suffix,
|
||||||
|
// such as "300s", "1.5h" or "5d".
|
||||||
|
// Valid time units are "s", "m", "h", "d", "w".
|
||||||
|
type Duration struct {
|
||||||
|
time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalTOML parses a duration string.
|
||||||
|
func (d *Duration) UnmarshalTOML(dataInterface interface{}) error {
|
||||||
|
var data string
|
||||||
|
switch dataInterface.(type) {
|
||||||
|
case string:
|
||||||
|
data = dataInterface.(string)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid duration: \"%s\"", dataInterface)
|
||||||
|
}
|
||||||
|
// " + int + unit + "
|
||||||
|
if len(data) < 2 {
|
||||||
|
return fmt.Errorf("invalid duration: \"%s\"", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
unit := data[len(data)-1]
|
||||||
|
value, err := strconv.Atoi(string(data[:len(data)-1]))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse duration %s: %s", data, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch unit {
|
||||||
|
case 's':
|
||||||
|
d.Duration = time.Duration(value) * time.Second
|
||||||
|
case 'm':
|
||||||
|
d.Duration = time.Duration(value) * time.Minute
|
||||||
|
case 'h':
|
||||||
|
d.Duration = time.Duration(value) * time.Hour
|
||||||
|
case 'd':
|
||||||
|
d.Duration = time.Duration(value) * time.Hour * 24
|
||||||
|
case 'w':
|
||||||
|
d.Duration = time.Duration(value) * time.Hour * 24 * 7
|
||||||
|
case 'y':
|
||||||
|
d.Duration = time.Duration(value) * time.Hour * 24 * 365
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid duration unit: %s", string(unit))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDuration(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
var tests = []struct {
|
||||||
|
input string
|
||||||
|
err string
|
||||||
|
duration time.Duration
|
||||||
|
}{
|
||||||
|
{"", "invalid duration: \"\"", 0},
|
||||||
|
{"1x", "invalid duration unit: x", 0},
|
||||||
|
{"1s", "", time.Second},
|
||||||
|
{"73s", "", time.Second * 73},
|
||||||
|
{"1m", "", time.Minute},
|
||||||
|
{"73m", "", time.Minute * 73},
|
||||||
|
{"1h", "", time.Hour},
|
||||||
|
{"43h", "", time.Hour * 43},
|
||||||
|
{"1d", "", time.Hour * 24},
|
||||||
|
{"8d", "", time.Hour * 24 * 8},
|
||||||
|
{"1w", "", time.Hour * 24 * 7},
|
||||||
|
{"52w", "", time.Hour * 24 * 7 * 52},
|
||||||
|
{"1y", "", time.Hour * 24 * 365},
|
||||||
|
{"3y", "", time.Hour * 24 * 365 * 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
|
||||||
|
d := Duration{}
|
||||||
|
err := d.UnmarshalTOML(test.input)
|
||||||
|
duration := d.Duration
|
||||||
|
|
||||||
|
if test.err == "" {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(test.duration, duration)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(err, test.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,49 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import "github.com/genofire/hs_master-kss-monolith/lib/database"
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
|
||||||
|
"github.com/genofire/hs_master-kss-monolith/lib/database"
|
||||||
|
)
|
||||||
|
|
||||||
type Good struct {
|
type Good struct {
|
||||||
ID int64
|
ID int64
|
||||||
ProductID int64
|
ProductID int64
|
||||||
|
Position string
|
||||||
Comment string
|
Comment string
|
||||||
|
FouledAt *time.Time
|
||||||
|
|
||||||
|
RecievedAt *time.Time `sql:"default:current_timestamp"`
|
||||||
|
// Make it temporary unusable
|
||||||
|
LockedAt *time.Time
|
||||||
|
LockedSecret string
|
||||||
|
// Make it unusable
|
||||||
|
DeletedAt *time.Time
|
||||||
|
Sended bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Good) FilterAvailable(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("locked_secret is NULL deleted_at is NULL and send_at is NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Good) Lock(secret string) {
|
||||||
|
now := time.Now()
|
||||||
|
g.LockedSecret = secret
|
||||||
|
g.LockedAt = &now
|
||||||
|
}
|
||||||
|
func (g *Good) IsLock() bool {
|
||||||
|
return len(g.LockedSecret) > 0
|
||||||
|
}
|
||||||
|
func (g *Good) Unlock(secret string) error {
|
||||||
|
if g.LockedSecret == secret {
|
||||||
|
g.LockedSecret = ""
|
||||||
|
g.LockedAt = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("wrong secret")
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/genofire/hs_master-kss-monolith/lib/database"
|
||||||
|
"github.com/genofire/hs_master-kss-monolith/lib/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GoodReleaseConfig struct {
|
||||||
|
After Duration `toml:"after"`
|
||||||
|
Timer Duration `toml:"timer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GoodReleaseWorker struct {
|
||||||
|
unlockTimer time.Duration
|
||||||
|
unlockAfter time.Duration
|
||||||
|
quit chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGoodReleaseWorker(grc GoodReleaseConfig) (rw *GoodReleaseWorker) {
|
||||||
|
rw = &GoodReleaseWorker{
|
||||||
|
unlockTimer: grc.Timer.Duration,
|
||||||
|
unlockAfter: grc.After.Duration,
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *GoodReleaseWorker) Start() {
|
||||||
|
ticker := time.NewTicker(rw.unlockTimer)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
count := goodRelease(rw.unlockAfter)
|
||||||
|
log.Log.WithField("count", count).Info("goods released")
|
||||||
|
case <-rw.quit:
|
||||||
|
ticker.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *GoodReleaseWorker) Close() {
|
||||||
|
close(rw.quit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func goodRelease(unlockAfter time.Duration) int64 {
|
||||||
|
res := database.Write.Model(&Good{}).Where("locked_secret is not NULL and locked_at < ?", time.Now().Add(-unlockAfter)).Updates(map[string]interface{}{"locked_secret": "", "locked_at": nil})
|
||||||
|
return res.RowsAffected
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/genofire/hs_master-kss-monolith/lib/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGoodRelease(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
database.Open(database.Config{
|
||||||
|
Type: "sqlite3",
|
||||||
|
Logging: true,
|
||||||
|
Connection: ":memory:",
|
||||||
|
})
|
||||||
|
now := time.Now()
|
||||||
|
good := Good{
|
||||||
|
LockedAt: &now,
|
||||||
|
LockedSecret: "never used",
|
||||||
|
}
|
||||||
|
database.Write.Create(&good)
|
||||||
|
count := goodRelease(time.Duration(3) * time.Second)
|
||||||
|
assert.Equal(int64(0), count, "no locked in timeout")
|
||||||
|
|
||||||
|
older := now.Add(-time.Duration(10) * time.Minute)
|
||||||
|
good.LockedAt = &older
|
||||||
|
database.Write.Save(&good)
|
||||||
|
count = goodRelease(time.Duration(3) * time.Second)
|
||||||
|
assert.Equal(int64(1), count, "unlock after timeout")
|
||||||
|
|
||||||
|
grw := NewGoodReleaseWorker(GoodReleaseConfig{
|
||||||
|
Timer: Duration{Duration: time.Duration(3) * time.Millisecond},
|
||||||
|
After: Duration{Duration: time.Duration(5) * time.Millisecond},
|
||||||
|
})
|
||||||
|
go grw.Start()
|
||||||
|
time.Sleep(time.Duration(15) * time.Millisecond)
|
||||||
|
grw.Close()
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGood(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
good := &Good{}
|
||||||
|
assert.False(good.IsLock())
|
||||||
|
|
||||||
|
good.Lock("blub_secret")
|
||||||
|
assert.True(good.IsLock())
|
||||||
|
|
||||||
|
err := good.Unlock("secret")
|
||||||
|
assert.Error(err)
|
||||||
|
assert.True(good.IsLock())
|
||||||
|
|
||||||
|
good.Unlock("blub_secret")
|
||||||
|
assert.False(good.IsLock())
|
||||||
|
}
|
|
@ -1,36 +1,5 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
type Product struct {
|
||||||
"net/http"
|
ID int64
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type boolMicroServiceCache struct {
|
|
||||||
LastCheck time.Time
|
|
||||||
Value bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var productExistCache map[int64]boolMicroServiceCache
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
productExistCache = make(map[int64]boolMicroServiceCache)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProductExists(id int64) (bool, error) {
|
|
||||||
if cache, ok := productExistCache[id]; ok {
|
|
||||||
// cache for 5min
|
|
||||||
before := time.Now().Add(-time.Minute * 5)
|
|
||||||
if !cache.LastCheck.Before(before) {
|
|
||||||
return cache.Value, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO DRAFT for a rest request to a other microservice
|
|
||||||
res, err := http.Get("http://golang.org")
|
|
||||||
|
|
||||||
productExistCache[id] = boolMicroServiceCache{
|
|
||||||
LastCheck: time.Now(),
|
|
||||||
Value: (res.StatusCode == http.StatusOK),
|
|
||||||
}
|
|
||||||
return productExistCache[id].Value, err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/genofire/hs_master-kss-monolith/lib/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO DRAFT for a rest request to a other microservice
|
||||||
|
const ProductURL = "https://google.com/?q=%d"
|
||||||
|
|
||||||
|
type boolMicroServiceCache struct {
|
||||||
|
LastCheck time.Time
|
||||||
|
Value bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var productExistCache map[int64]boolMicroServiceCache
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
productExistCache = make(map[int64]boolMicroServiceCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Product) Exists() (bool, error) {
|
||||||
|
if cache, ok := productExistCache[p.ID]; ok {
|
||||||
|
// cache for 5min
|
||||||
|
before := time.Now().Add(-time.Minute * 5)
|
||||||
|
if !cache.LastCheck.Before(before) {
|
||||||
|
return cache.Value, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf(ProductURL, p.ID)
|
||||||
|
log.Log.WithField("url", url).Info("exists product?")
|
||||||
|
res, err := http.Get(url)
|
||||||
|
|
||||||
|
productExistCache[p.ID] = boolMicroServiceCache{
|
||||||
|
LastCheck: time.Now(),
|
||||||
|
Value: (res.StatusCode == http.StatusOK),
|
||||||
|
}
|
||||||
|
return productExistCache[p.ID].Value, err
|
||||||
|
}
|
|
@ -9,12 +9,12 @@ import (
|
||||||
func TestProductExists(t *testing.T) {
|
func TestProductExists(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
ok, err := ProductExists(3)
|
ok, err := (&Product{ID: 3}).Exists()
|
||||||
assert.True(ok)
|
assert.True(ok)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
|
|
||||||
// test cache
|
// test cache
|
||||||
ok, err = ProductExists(3)
|
ok, err = (&Product{ID: 3}).Exists()
|
||||||
assert.True(ok)
|
assert.True(ok)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
|
|
||||||
|
|
Reference in New Issue