Merge pull request #49 from umputun/feature/application

Feature/application
This commit is contained in:
Umputun
2018-05-23 01:29:12 -05:00
committed by GitHub
15 changed files with 311 additions and 88 deletions
+1 -14
View File
@@ -1,19 +1,6 @@
language: go
go:
- "1.10.x"
before_install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
install:
- docker --version
- docker-compose --version
script:
- docker build .
after_success:
- go test $(go list -e ./... | grep -v vendor) -covermode=count -coverprofile=coverage.out
- goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN
- docker build --build-arg COVERALLS_TOKEN=$COVERALLS_TOKEN --build-arg TRAVIS_JOB_ID=$TRAVIS_JOB_ID --build-arg TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST --build-arg TRAVIS_BRANCH=$TRAVIS_BRANCH .
+11 -1
View File
@@ -1,9 +1,15 @@
FROM umputun/baseimage:buildgo-latest as build-backend
ARG COVERALLS_TOKEN
ARG TRAVIS_JOB_ID
ARG TRAVIS_PULL_REQUEST
ARG TRAVIS_BRANCH
WORKDIR /go/src/github.com/umputun/remark
ADD app /go/src/github.com/umputun/remark/app
ADD vendor /go/src/github.com/umputun/remark/vendor
ADD .git /go/src/github.com/umputun/remark/.git
RUN cd app && go test $(go list -e ./... | grep -v vendor)
@@ -13,7 +19,11 @@ RUN gometalinter --disable-all --deadline=300s --vendor --enable=vet --enable=ve
RUN mkdir -p target && /script/coverage.sh
ADD .git /go/src/github.com/umputun/remark/.git
RUN if [ "x$COVERALLS_TOKEN" = "x" ] ; then \
echo coverall not enabled ; \
else go get github.com/mattn/goveralls && \
goveralls -coverprofile=.cover/cover.out -service=travis-ci -repotoken $COVERALLS_TOKEN; fi
RUN go build -o remark -ldflags "-X main.revision=$(git rev-parse --abbrev-ref HEAD)-$(git describe --abbrev=7 --always --tags)-$(date +%Y%m%d-%H:%M:%S) -s -w" ./app
+105 -51
View File
@@ -1,11 +1,14 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/coreos/bbolt"
@@ -22,7 +25,8 @@ import (
"github.com/umputun/remark/app/rest/proxy"
)
var opts struct {
// Opts with command line flags and env
type Opts struct {
BoltPath string `long:"bolt" env:"BOLTDB_PATH" default:"./var" description:"parent dir for bolt files"`
Sites []string `long:"site" env:"SITE" default:"remark" description:"site names" env-delim:","`
RemarkURL string `long:"url" env:"REMARK_URL" default:"https://remark42.com" description:"url to remark"`
@@ -58,47 +62,62 @@ var opts struct {
var revision = "unknown"
// Application holds all active objects
type Application struct {
Opts
srv *api.Rest
importer *api.Import
exporter migrator.Exporter
terminated chan struct{}
}
func main() {
fmt.Printf("remark %s\n", revision)
var opts Opts
p := flags.NewParser(&opts, flags.Default)
if _, e := p.ParseArgs(os.Args[1:]); e != nil {
os.Exit(1)
}
setupLog(opts.Dbg)
log.Print("[INFO] started remark")
if err := makeDirs(opts.BoltPath, opts.BackupLocation, opts.AvatarStore); err != nil {
log.Fatalf("[ERROR] can't create directories, %+v", err)
ctx, cancel := context.WithCancel(context.Background())
go func() { // catch signal and invoke graceful termination
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
<-stop
log.Print("[WARN] interrupt signal")
cancel()
}()
app, err := New(opts)
if err != nil {
log.Fatalf("[ERROR] failed to setup application, %+v", err)
}
err = app.Run(ctx)
log.Printf("[INFO] remark terminated %s", err)
}
dataStore := makeBoltStore(opts.Sites)
// New prepares application and return it with all active parts
// doesn't start anything
func New(opts Opts) (*Application, error) {
setupLog(opts.Dbg)
if opts.DevPasswd != "" {
log.Printf("[WARN] running in dev mode")
if err := makeDirs(opts.BoltPath, opts.BackupLocation, opts.AvatarStore); err != nil {
return nil, err
}
dataService := service.DataStore{
Interface: dataStore,
Interface: makeBoltStore(opts.Sites, opts.BoltPath),
EditDuration: 5 * time.Minute,
Secret: opts.SecretKey,
MaxCommentSize: opts.MaxCommentSize,
}
exporter := migrator.Remark{DataStore: &dataService}
cache := rest.NewLoadingCache(rest.MaxValueSize(opts.MaxCachedValue), rest.MaxKeys(opts.MaxCachedItems),
rest.PostFlushFn(postFlushFn))
cache := rest.NewLoadingCache(rest.MaxValSize(opts.MaxCachedValue), rest.MaxKeys(opts.MaxCachedItems),
rest.PostFlushFn(postFlushFn(opts.Sites, opts.Port)))
activateBackup(&exporter)
importSrv := api.Import{
Version: revision,
Cache: cache,
NativeImporter: &migrator.Remark{DataStore: &dataService},
DisqusImporter: &migrator.Disqus{DataStore: &dataService},
SecretKey: opts.SecretKey,
}
go importSrv.Run(opts.Port + 1)
jwtService := auth.NewJWT(opts.SecretKey, strings.HasPrefix(opts.RemarkURL, "https://"), 7*24*time.Hour)
avatarProxy := &proxy.Avatar{
StorePath: opts.AvatarStore,
@@ -106,46 +125,79 @@ func main() {
RemarkURL: strings.TrimSuffix(opts.RemarkURL, "/"),
}
jwtService := auth.NewJWT(opts.SecretKey, strings.HasPrefix(opts.RemarkURL, "https://"), 7*24*time.Hour)
exporter := &migrator.Remark{DataStore: &dataService}
srv := api.Rest{
importer := &api.Import{
Version: revision,
Cache: cache,
NativeImporter: &migrator.Remark{DataStore: &dataService},
DisqusImporter: &migrator.Disqus{DataStore: &dataService},
SecretKey: opts.SecretKey,
}
srv := &api.Rest{
Version: revision,
DataService: dataService,
Exporter: &exporter,
Exporter: exporter,
WebRoot: opts.WebRoot,
ImageProxy: proxy.Image{Enabled: opts.ImageProxy, RoutePath: "/api/v1/img", RemarkURL: opts.RemarkURL},
Authenticator: auth.Authenticator{
JWTService: jwtService,
Admins: opts.Admins,
Providers: makeAuthProviders(jwtService, avatarProxy),
Providers: makeAuthProviders(jwtService, avatarProxy, opts),
AvatarProxy: avatarProxy,
DevPasswd: opts.DevPasswd,
},
Cache: cache,
}
srv.ScoreThresholds.Low, srv.ScoreThresholds.Critical = opts.LowScore, opts.CriticalScore
srv.Run(opts.Port)
tch := make(chan struct{})
return &Application{srv: srv, importer: importer, exporter: exporter, Opts: opts, terminated: tch}, nil
}
// Run all application objects
func (a *Application) Run(ctx context.Context) error {
if a.DevPasswd != "" {
log.Printf("[WARN] running in dev mode")
}
go func() {
// shutdown on context cancellation
<-ctx.Done()
a.srv.Shutdown()
a.importer.Shutdown()
}()
a.activateBackup(ctx) // runs in goroutine for each site
go a.importer.Run(a.Port + 1)
a.srv.Run(a.Port)
close(a.terminated)
return nil
}
// Wait for application completion (termination)
func (a *Application) Wait() {
<-a.terminated
}
// activateBackup runs background backups for each site
func activateBackup(exporter migrator.Exporter) {
for _, siteID := range opts.Sites {
func (a *Application) activateBackup(ctx context.Context) {
for _, siteID := range a.Sites {
backup := migrator.AutoBackup{
Exporter: exporter,
BackupLocation: opts.BackupLocation,
Exporter: a.exporter,
BackupLocation: a.BackupLocation,
SiteID: siteID,
KeepMax: opts.MaxBackupFiles,
KeepMax: a.MaxBackupFiles,
Duration: 24 * time.Hour,
}
go backup.Do()
go backup.Do(ctx)
}
}
// makeBoltStore creates store for all sites
func makeBoltStore(siteNames []string) engine.Interface {
func makeBoltStore(siteNames []string, path string) engine.Interface {
sites := []engine.BoltSite{}
for _, site := range siteNames {
sites = append(sites, engine.BoltSite{SiteID: site, FileName: fmt.Sprintf("%s/%s.db", opts.BoltPath, site)})
sites = append(sites, engine.BoltSite{SiteID: site, FileName: fmt.Sprintf("%s/%s.db", path, site)})
}
result, err := engine.NewBoltDB(bolt.Options{Timeout: 30 * time.Second}, sites...)
if err != nil {
@@ -183,7 +235,7 @@ func makeDirs(dirs ...string) error {
return nil
}
func makeAuthProviders(jwtService *auth.JWT, avatarProxy *proxy.Avatar) (providers []auth.Provider) {
func makeAuthProviders(jwtService *auth.JWT, avatarProxy *proxy.Avatar, opts Opts) (providers []auth.Provider) {
makeParams := func(cid, secret string) auth.Params {
return auth.Params{
@@ -214,23 +266,25 @@ func makeAuthProviders(jwtService *auth.JWT, avatarProxy *proxy.Avatar) (provide
}
// post-flush callback invoked by cache after each flush in async way
func postFlushFn() {
func postFlushFn(sites []string, port int) func() {
// list of heavy urls for pre-heating on cache change
urls := []string{
"http://localhost:%d/api/v1/list?site=%s",
"http://localhost:%d/api/v1/last/50?site=%s",
}
return func() {
// list of heavy urls for pre-heating on cache change
urls := []string{
"http://localhost:%d/api/v1/list?site=%s",
"http://localhost:%d/api/v1/last/50?site=%s",
}
for _, site := range opts.Sites {
for _, u := range urls {
resp, err := http.Get(fmt.Sprintf(u, opts.Port, site))
if err != nil {
log.Printf("[WARN] failed to refresh cached list for %s, %s", site, err)
return
}
if err = resp.Body.Close(); err != nil {
log.Printf("[WARN] failed to close response body, %s", err)
for _, site := range sites {
for _, u := range urls {
resp, err := http.Get(fmt.Sprintf(u, port, site))
if err != nil {
log.Printf("[WARN] failed to refresh cached list for %s, %s", site, err)
return
}
if err = resp.Body.Close(); err != nil {
log.Printf("[WARN] failed to close response body, %s", err)
}
}
}
}
+88
View File
@@ -0,0 +1,88 @@
package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"syscall"
"testing"
"time"
flags "github.com/jessevdk/go-flags"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestApplication(t *testing.T) {
app, ctx := prepApp(t, 18080, 500*time.Millisecond)
go app.Run(ctx)
time.Sleep(100 * time.Millisecond) // let server start
// send ping
resp, err := http.Get("http://localhost:18080/api/v1/ping")
require.Nil(t, err)
defer resp.Body.Close()
assert.Equal(t, 200, resp.StatusCode)
body, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
assert.Equal(t, "pong", string(body))
// add comment
resp, err = http.Post("http://dev:password@localhost:18080/api/v1/comment", "json",
strings.NewReader(`{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "remark"}}`))
require.Nil(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
body, _ = ioutil.ReadAll(resp.Body)
t.Log(string(body))
app.Wait()
}
func TestApplicationShutdown(t *testing.T) {
app, ctx := prepApp(t, 18090, 500*time.Millisecond)
st := time.Now()
app.Run(ctx)
assert.True(t, time.Since(st).Seconds() < 1, "should take about 500msec")
app.Wait()
}
func TestApplicationMainSignal(t *testing.T) {
os.Args = []string{"test", "--secret=123456", "--bolt=/tmp/xyz", "--backup=/tmp", "--avatars=/tmp", "--port=18100"}
go func() {
time.Sleep(100 * time.Millisecond)
syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
}()
st := time.Now()
main()
assert.True(t, time.Since(st).Seconds() < 1, "should take about 500msec")
}
func prepApp(t *testing.T, port int, duration time.Duration) (*Application, context.Context) {
// prepare options
opts := Opts{}
p := flags.NewParser(&opts, flags.Default)
p.ParseArgs([]string{"--secret=123456", "--dev-passwd=password"})
opts.AvatarStore, opts.BackupLocation = "/tmp", "/tmp"
opts.BoltPath = fmt.Sprintf("/tmp/%d", port)
opts.GithubCSEC, opts.GithubCID = "csec", "cid"
opts.GoogleCSEC, opts.GoogleCID = "csec", "cid"
opts.FacebookCSEC, opts.FacebookCID = "csec", "cid"
opts.Port = port
os.Remove(opts.BoltPath + "/remark.db")
// create app
app, err := New(opts)
require.Nil(t, err)
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(duration)
log.Print("[TEST] terminate app")
cancel()
}()
return app, ctx
}
+15 -7
View File
@@ -2,6 +2,7 @@ package migrator
import (
"compress/gzip"
"context"
"fmt"
"io/ioutil"
"log"
@@ -23,17 +24,24 @@ type AutoBackup struct {
}
// Do runs daily export to local files, keeps up to keepMax backups for given siteID
func (ab AutoBackup) Do() {
func (ab AutoBackup) Do(ctx context.Context) {
log.Printf("[INFO] activate auto-backup for %s", ab.BackupLocation)
tick := time.NewTicker(ab.Duration)
log.Printf("[DEBUG] first backup at %s", time.Now().Add(ab.Duration))
for range tick.C {
if _, err := ab.makeBackup(); err != nil {
log.Printf("[WARN] auto-backup for %s failed, %s", ab.SiteID, err)
continue
for {
select {
case <-tick.C:
if _, err := ab.makeBackup(); err != nil {
log.Printf("[WARN] auto-backup for %s failed, %s", ab.SiteID, err)
continue
}
ab.removeOldBackupFiles()
log.Printf("[DEBUG] next backup at %s", time.Now().Add(ab.Duration))
case <-ctx.Done():
log.Printf("[WARN] terminated autobackup for %s", ab.SiteID)
return
}
ab.removeOldBackupFiles()
log.Printf("[DEBUG] next backup at %s", time.Now().Add(ab.Duration))
}
}
+23 -2
View File
@@ -1,6 +1,7 @@
package migrator
import (
"context"
"fmt"
"io"
"io/ioutil"
@@ -11,7 +12,7 @@ import (
"github.com/stretchr/testify/assert"
)
func TestMigrator_RemoveOldBackupFiles(t *testing.T) {
func TestBackup_RemoveOldBackupFiles(t *testing.T) {
loc := "/tmp/remark-backups.test"
defer os.RemoveAll(loc)
@@ -36,7 +37,7 @@ func TestMigrator_RemoveOldBackupFiles(t *testing.T) {
assert.Equal(t, "backup-site2-20171210.gz", ff[3].Name())
}
func TestMigrator_MakeBackup(t *testing.T) {
func TestBackup_MakeBackup(t *testing.T) {
loc := "/tmp/remark-backups.test"
defer os.RemoveAll(loc)
os.MkdirAll(loc, 0700)
@@ -52,6 +53,26 @@ func TestMigrator_MakeBackup(t *testing.T) {
assert.Equal(t, int64(52), fi.Size())
}
func TestBackup_Do(t *testing.T) {
loc := "/tmp/remark-backups.test"
defer os.RemoveAll(loc)
os.MkdirAll(loc, 0700)
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(time.Second)
cancel()
}()
bk := AutoBackup{BackupLocation: loc, SiteID: "site1", KeepMax: 3, Exporter: &mockExporter{}, Duration: 600 * time.Millisecond}
bk.Do(ctx)
expFile := fmt.Sprintf("/tmp/remark-backups.test/backup-site1-%s.gz", time.Now().Format("20060102"))
fi, err := os.Lstat(expFile)
assert.NoError(t, err)
assert.Equal(t, int64(52), fi.Size())
}
type mockExporter struct{}
func (mock *mockExporter) Export(w io.Writer, siteID string) (int, error) {
+27 -3
View File
@@ -1,10 +1,12 @@
package api
import (
"context"
"fmt"
"log"
"net/http"
"strings"
"sync"
"time"
"github.com/didip/tollbooth"
@@ -24,6 +26,9 @@ type Import struct {
NativeImporter migrator.Importer
DisqusImporter migrator.Importer
SecretKey string
httpServer *http.Server
lock sync.Mutex
}
// Run the listener and request's router, activate rest server
@@ -31,12 +36,31 @@ type Import struct {
func (s *Import) Run(port int) {
log.Printf("[INFO] activate import server on port %d", port)
router := s.routes()
httpServer := &http.Server{Addr: fmt.Sprintf("127.0.0.1:%d", port), Handler: router}
err := httpServer.ListenAndServe()
s.lock.Lock()
s.httpServer = &http.Server{Addr: fmt.Sprintf("127.0.0.1:%d", port), Handler: router}
s.lock.Unlock()
err := s.httpServer.ListenAndServe()
log.Printf("[WARN] http server terminated, %s", err)
}
func (s Import) routes() chi.Router {
// Shutdown import http server
func (s *Import) Shutdown() {
log.Print("[WARN] shutdown import server")
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
s.lock.Lock()
if err := s.httpServer.Shutdown(ctx); err != nil {
log.Printf("[DEBUG] importer shutdown error, %s", err)
}
s.lock.Unlock()
log.Print("[DEBUG] shutdown import server completed")
}
func (s *Import) routes() chi.Router {
router := chi.NewRouter()
router.Use(middleware.RealIP, Recoverer)
router.Use(middleware.Throttle(1000), middleware.Timeout(60*time.Second))
+1
View File
@@ -30,6 +30,7 @@ func TestImport(t *testing.T) {
req, err := http.NewRequest("POST", ts.URL+"/api/v1/admin/import?site=radio-t&provider=native&secret=123456", r)
assert.Nil(t, err)
resp, err := client.Do(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
+21 -1
View File
@@ -2,6 +2,7 @@ package api
import (
"bytes"
"context"
"crypto/sha1"
"encoding/base64"
"encoding/json"
@@ -10,6 +11,7 @@ import (
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/didip/tollbooth"
@@ -43,7 +45,9 @@ type Rest struct {
Critical int
}
httpServer *http.Server
httpServer *http.Server
lock sync.Mutex
adminService admin
}
@@ -63,6 +67,7 @@ func (s *Rest) Run(port int) {
router := s.routes()
s.lock.Lock()
s.httpServer = &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: router,
@@ -70,10 +75,25 @@ func (s *Rest) Run(port int) {
WriteTimeout: 5 * time.Second,
IdleTimeout: 30 * time.Second,
}
s.lock.Unlock()
err := s.httpServer.ListenAndServe()
log.Printf("[WARN] http server terminated, %s", err)
}
// Shutdown rest http server
func (s *Rest) Shutdown() {
log.Print("[WARN] shutdown rest server")
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
s.lock.Lock()
if err := s.httpServer.Shutdown(ctx); err != nil {
log.Printf("[DEBUG] rest shutdown error, %s", err)
}
log.Print("[DEBUG] shutdown rest server completed")
s.lock.Unlock()
}
func (s *Rest) routes() chi.Router {
router := chi.NewRouter()
router.Use(middleware.RealIP, Recoverer)
+8
View File
@@ -87,17 +87,20 @@ func TestAuthRequired(t *testing.T) {
client := &http.Client{Timeout: 1 * time.Second}
req, err := http.NewRequest("GET", server.URL+"/auth", nil)
require.NoError(t, err)
req = withBasicAuth(req, "dev", "123456")
resp, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, 201, resp.StatusCode, "valid auth user")
req, err = http.NewRequest("GET", server.URL+"/auth", nil)
require.NoError(t, err)
resp, err = client.Do(req)
require.NoError(t, err)
assert.Equal(t, 401, resp.StatusCode, "no auth user")
req, err = http.NewRequest("GET", server.URL+"/auth", nil)
require.NoError(t, err)
req = withBasicAuth(req, "dev", "xyz")
resp, err = client.Do(req)
require.NoError(t, err)
@@ -115,17 +118,20 @@ func TestAuthNotRequired(t *testing.T) {
client := &http.Client{Timeout: 1 * time.Second}
req, err := http.NewRequest("GET", server.URL+"/auth", nil)
require.NoError(t, err)
req = withBasicAuth(req, "dev", "123456")
resp, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, 201, resp.StatusCode, "valid auth user")
req, err = http.NewRequest("GET", server.URL+"/auth", nil)
require.NoError(t, err)
resp, err = client.Do(req)
require.NoError(t, err)
assert.Equal(t, 201, resp.StatusCode, "no auth user")
req, err = http.NewRequest("GET", server.URL+"/auth", nil)
require.NoError(t, err)
req = withBasicAuth(req, "dev", "ZZZZ123456")
resp, err = client.Do(req)
require.NoError(t, err)
@@ -143,6 +149,7 @@ func TestAdminRequired(t *testing.T) {
client := &http.Client{Timeout: 1 * time.Second}
req, err := http.NewRequest("GET", server.URL+"/auth", nil)
require.NoError(t, err)
req = withBasicAuth(req, "dev", "123456")
resp, err := client.Do(req)
require.NoError(t, err)
@@ -150,6 +157,7 @@ func TestAdminRequired(t *testing.T) {
devUser.Admin = false
req, err = http.NewRequest("GET", server.URL+"/auth", nil)
require.NoError(t, err)
req = withBasicAuth(req, "dev", "123456")
resp, err = client.Do(req)
require.NoError(t, err)
+1
View File
@@ -27,6 +27,7 @@ func TestLogin(t *testing.T) {
}()
jar, err := cookiejar.New(nil)
require.Nil(t, err)
client := &http.Client{Jar: jar, Timeout: 5 * time.Second}
resp, err := client.Get("http://localhost:8981/login")
assert.Nil(t, err)
+2 -2
View File
@@ -82,9 +82,9 @@ func (lc *loadingCache) allowed(data []byte) bool {
// CacheOption func type
type CacheOption func(lc *loadingCache) error
// MaxValueSize functional option defines the largest value's size allowed to be cached
// MaxValSize functional option defines the largest value's size allowed to be cached
// By default it is 0, which means unlimited.
func MaxValueSize(max int) CacheOption {
func MaxValSize(max int) CacheOption {
return func(lc *loadingCache) error {
lc.maxValueSize = max
return nil
+3 -2
View File
@@ -43,7 +43,7 @@ func TestLoadingCache_Get(t *testing.T) {
func TestLoadingCache_MaxKeys(t *testing.T) {
var postFnCall, coldCalls int32
lc := NewLoadingCache(CleanupInterval(200*time.Millisecond), PostFlushFn(func() { atomic.AddInt32(&postFnCall, 1) }),
MaxKeys(5), MaxValueSize(10))
MaxKeys(5), MaxValSize(10))
// put 5 keys to cache
for i := 0; i < 5; i++ {
@@ -97,7 +97,7 @@ func TestLoadingCache_MaxKeys(t *testing.T) {
}
func TestLoadingCache_MaxSize(t *testing.T) {
lc := NewLoadingCache(CleanupInterval(200*time.Millisecond), MaxKeys(5), MaxValueSize(10))
lc := NewLoadingCache(CleanupInterval(200*time.Millisecond), MaxKeys(5), MaxValSize(10))
// put good size value to cache and make sure it cached
res, err := lc.Get("key-Z", time.Minute, func() ([]byte, error) {
@@ -157,6 +157,7 @@ func TestLoadingCache_Parallel(t *testing.T) {
wg := sync.WaitGroup{}
for i := 0; i < 1000; i++ {
wg.Add(1)
i := i
go func() {
defer wg.Done()
res, err := lc.Get("key", time.Minute, func() ([]byte, error) {
+2 -2
View File
@@ -62,7 +62,7 @@ func TestImage_Routes(t *testing.T) {
img := Image{Enabled: true, RemarkURL: "https://demo.remark42.com", RoutePath: "/api/v1/proxy"}
router := img.Routes()
httpSrv := imgHttpServer(t)
httpSrv := imgHTTPServer(t)
defer httpSrv.Close()
ts := httptest.NewServer(router)
defer ts.Close()
@@ -88,7 +88,7 @@ func TestPicture_Convert(t *testing.T) {
assert.Equal(t, `<img src="/img?src=aHR0cDovL3JhZGlvLXQuY29tL2ltZzMucG5n"/> xyz <img src="/img?src=aHR0cDovL2ltYWdlcy5wZXhlbHMuY29tLzY3NjM2L2ltZzQuanBlZw==">`, r)
}
func imgHttpServer(t *testing.T) *httptest.Server {
func imgHTTPServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/image/img1.png" {
t.Log("http img request", r.URL)
+3 -3
View File
@@ -14,8 +14,8 @@ import (
var testDb = "/tmp/test-remark.db"
func TestBoltDB_CreateAndFind(t *testing.T) {
var b = prep(t)
defer os.Remove(testDb)
var b = prep(t)
res, err := b.Find(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, "time")
assert.Nil(t, err)
@@ -30,8 +30,8 @@ func TestBoltDB_CreateAndFind(t *testing.T) {
}
func TestBoltDB_Delete(t *testing.T) {
defer os.Remove(testDb)
b := prep(t)
defer os.Remove(testDb)
loc := store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}
res, err := b.Find(loc, "time")
@@ -218,7 +218,7 @@ func TestBoltDB_GetForUser(t *testing.T) {
func prep(t *testing.T) *BoltDB {
os.Remove(testDb)
boltStore, err := NewBoltDB(bolt.Options{}, BoltSite{FileName: "/tmp/test-remark.db", SiteID: "radio-t"})
boltStore, err := NewBoltDB(bolt.Options{}, BoltSite{FileName: testDb, SiteID: "radio-t"})
assert.Nil(t, err)
b := boltStore