tests (boilerplate)
This commit is contained in:
parent
1bc847cdc9
commit
8ea3fdcfef
@ -1,13 +1,16 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/api/apierr"
|
||||
"blackforestbytes.com/simplecloudnotifier/common/ginresp"
|
||||
"blackforestbytes.com/simplecloudnotifier/logic"
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
sqlite3 "github.com/mattn/go-sqlite3"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CommonHandler struct {
|
||||
@ -106,7 +109,7 @@ func (h CommonHandler) DatabaseTest(g *gin.Context) ginresp.HTTPResponse {
|
||||
// @Failure 500 {object} ginresp.apiError
|
||||
//
|
||||
// @Router /api/health [get]
|
||||
func (h CommonHandler) Health(*gin.Context) ginresp.HTTPResponse {
|
||||
func (h CommonHandler) Health(g *gin.Context) ginresp.HTTPResponse {
|
||||
type response struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
@ -125,6 +128,45 @@ func (h CommonHandler) Health(*gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.JSON(http.StatusOK, response{Status: "ok"})
|
||||
}
|
||||
|
||||
// Sleep swaggerdoc
|
||||
//
|
||||
// @Summary Return 200 after x seconds
|
||||
// @ID api-common-sleep
|
||||
// @Tags Common
|
||||
//
|
||||
// @Success 200 {object} handler.Sleep.response
|
||||
// @Failure 400 {object} ginresp.apiError
|
||||
// @Failure 500 {object} ginresp.apiError
|
||||
//
|
||||
// @Router /api/sleep/{secs} [post]
|
||||
func (h CommonHandler) Sleep(g *gin.Context) ginresp.HTTPResponse {
|
||||
type uri struct {
|
||||
Seconds float64 `uri:"secs"`
|
||||
}
|
||||
type response struct {
|
||||
Start string `json:"start"`
|
||||
End string `json:"end"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
|
||||
t0 := time.Now().Format(time.RFC3339Nano)
|
||||
|
||||
var u uri
|
||||
if err := g.ShouldBindUri(&u); err != nil {
|
||||
return ginresp.APIError(g, 400, apierr.BINDFAIL_URI_PARAM, "Failed to read uri", err)
|
||||
}
|
||||
|
||||
time.Sleep(timeext.FromSecondsFloat64(u.Seconds))
|
||||
|
||||
t1 := time.Now().Format(time.RFC3339Nano)
|
||||
|
||||
return ginresp.JSON(http.StatusOK, response{
|
||||
Start: t0,
|
||||
End: t1,
|
||||
Duration: u.Seconds,
|
||||
})
|
||||
}
|
||||
|
||||
func (h CommonHandler) NoRoute(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.JSON(http.StatusNotFound, gin.H{
|
||||
"": "================ ROUTE NOT FOUND ================",
|
||||
|
@ -53,6 +53,7 @@ func (r *Router) Init(e *gin.Engine) {
|
||||
commonAPI.Any("/ping", ginresp.Wrap(r.commonHandler.Ping))
|
||||
commonAPI.POST("/db-test", ginresp.Wrap(r.commonHandler.DatabaseTest))
|
||||
commonAPI.GET("/health", ginresp.Wrap(r.commonHandler.Health))
|
||||
commonAPI.POST("/sleep/:secs", ginresp.Wrap(r.commonHandler.Sleep))
|
||||
}
|
||||
|
||||
// ================ Swagger ================
|
||||
|
@ -20,7 +20,7 @@ func main() {
|
||||
|
||||
log.Info().Msg(fmt.Sprintf("Starting with config-namespace <%s>", conf.Namespace))
|
||||
|
||||
sqlite, err := db.NewDatabase(conf)
|
||||
sqlite, err := db.NewDatabase(conf.DBFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
scn "blackforestbytes.com/simplecloudnotifier"
|
||||
"blackforestbytes.com/simplecloudnotifier/db/schema"
|
||||
"context"
|
||||
"database/sql"
|
||||
@ -15,8 +14,8 @@ type Database struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewDatabase(conf scn.Config) (*Database, error) {
|
||||
db, err := sql.Open("sqlite3", conf.DBFile)
|
||||
func NewDatabase(filename string) (*Database, error) {
|
||||
db, err := sql.Open("sqlite3", filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -29,11 +29,14 @@ type Application struct {
|
||||
Database *db.Database
|
||||
Firebase push.NotificationClient
|
||||
Jobs []Job
|
||||
stopChan chan bool
|
||||
Port string
|
||||
}
|
||||
|
||||
func NewApp(db *db.Database) *Application {
|
||||
return &Application{
|
||||
Database: db,
|
||||
stopChan: make(chan bool, 8),
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +47,10 @@ func (app *Application) Init(cfg scn.Config, g *gin.Engine, fb push.Notification
|
||||
app.Jobs = jobs
|
||||
}
|
||||
|
||||
func (app *Application) Stop() {
|
||||
app.stopChan <- true
|
||||
}
|
||||
|
||||
func (app *Application) Run() {
|
||||
httpserver := &http.Server{
|
||||
Addr: net.JoinHostPort(app.Config.ServerIP, app.Config.ServerPort),
|
||||
@ -53,8 +60,24 @@ func (app *Application) Run() {
|
||||
errChan := make(chan error)
|
||||
|
||||
go func() {
|
||||
log.Info().Str("address", httpserver.Addr).Msg("HTTP-Server started on http://localhost:" + app.Config.ServerPort)
|
||||
errChan <- httpserver.ListenAndServe()
|
||||
|
||||
ln, err := net.Listen("tcp", httpserver.Addr)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
_, port, err := net.SplitHostPort(ln.Addr().String())
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Str("address", httpserver.Addr).Msg("HTTP-Server started on http://localhost:" + port)
|
||||
|
||||
app.Port = port
|
||||
|
||||
errChan <- httpserver.Serve(ln)
|
||||
}()
|
||||
|
||||
stop := make(chan os.Signal, 1)
|
||||
@ -81,10 +104,24 @@ func (app *Application) Run() {
|
||||
|
||||
case err := <-errChan:
|
||||
log.Error().Err(err).Msg("HTTP-Server failed")
|
||||
|
||||
case _ = <-app.stopChan:
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
log.Info().Msg("Manually stopping HTTP-Server")
|
||||
|
||||
err := httpserver.Shutdown(ctx)
|
||||
|
||||
if err != nil {
|
||||
log.Info().Err(err).Msg("Error while stopping the http-server")
|
||||
} else {
|
||||
log.Info().Msg("Manually stopped HTTP-Server")
|
||||
}
|
||||
}
|
||||
|
||||
for _, job := range app.Jobs {
|
||||
job.Start()
|
||||
job.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
|
37
server/push/testSink.go
Normal file
37
server/push/testSink.go
Normal file
@ -0,0 +1,37 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"context"
|
||||
_ "embed"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
)
|
||||
|
||||
type SinkData struct {
|
||||
Message models.Message
|
||||
Client models.Client
|
||||
}
|
||||
|
||||
type TestSink struct {
|
||||
data []SinkData
|
||||
}
|
||||
|
||||
func NewTestSink() NotificationClient {
|
||||
return &TestSink{}
|
||||
}
|
||||
|
||||
func (d *TestSink) SendNotification(ctx context.Context, client models.Client, msg models.Message) (string, error) {
|
||||
id, err := langext.NewHexUUID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
key := "TestSink[" + id + "]"
|
||||
|
||||
d.data = append(d.data, SinkData{
|
||||
Message: msg,
|
||||
Client: client,
|
||||
})
|
||||
|
||||
return key, nil
|
||||
}
|
@ -785,6 +785,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/sleep/{secs}": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Common"
|
||||
],
|
||||
"summary": "Return 200 after x seconds",
|
||||
"operationId": "api-common-sleep",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.Sleep.response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/update.php": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -2468,6 +2497,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.Sleep.response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "number"
|
||||
},
|
||||
"end": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.Update.response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -228,6 +228,15 @@ definitions:
|
||||
user_key:
|
||||
type: string
|
||||
type: object
|
||||
handler.Sleep.response:
|
||||
properties:
|
||||
duration:
|
||||
type: number
|
||||
end:
|
||||
type: string
|
||||
start:
|
||||
type: string
|
||||
type: object
|
||||
handler.Update.response:
|
||||
properties:
|
||||
is_pro:
|
||||
@ -962,6 +971,25 @@ paths:
|
||||
summary: Return all not-acknowledged messages
|
||||
tags:
|
||||
- API-v1
|
||||
/api/sleep/{secs}:
|
||||
post:
|
||||
operationId: api-common-sleep
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.Sleep.response'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
summary: Return 200 after x seconds
|
||||
tags:
|
||||
- Common
|
||||
/api/update.php:
|
||||
get:
|
||||
deprecated: true
|
||||
|
103
server/test/common_test.go
Normal file
103
server/test/common_test.go
Normal file
@ -0,0 +1,103 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
scn "blackforestbytes.com/simplecloudnotifier"
|
||||
"blackforestbytes.com/simplecloudnotifier/api"
|
||||
"blackforestbytes.com/simplecloudnotifier/common/ginext"
|
||||
"blackforestbytes.com/simplecloudnotifier/db"
|
||||
"blackforestbytes.com/simplecloudnotifier/jobs"
|
||||
"blackforestbytes.com/simplecloudnotifier/logic"
|
||||
"blackforestbytes.com/simplecloudnotifier/push"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewSimpleWebserver(t *testing.T) *logic.Application {
|
||||
|
||||
uuid, err := langext.NewHexUUID()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
dbfile := filepath.Join(os.TempDir(), uuid+"sqlite3")
|
||||
defer func() {
|
||||
_ = os.Remove(dbfile)
|
||||
}()
|
||||
|
||||
conf := scn.Config{
|
||||
Namespace: "test",
|
||||
GinDebug: true,
|
||||
ServerIP: "0.0.0.0",
|
||||
ServerPort: "0", // simply choose a free port
|
||||
DBFile: dbfile,
|
||||
RequestTimeout: 500 * time.Millisecond,
|
||||
ReturnRawErrors: true,
|
||||
DummyFirebase: true,
|
||||
}
|
||||
|
||||
sqlite, err := db.NewDatabase(dbfile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
app := logic.NewApp(sqlite)
|
||||
|
||||
if err := app.Migrate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ginengine := ginext.NewEngine(conf)
|
||||
|
||||
router := api.NewRouter(app)
|
||||
|
||||
nc := push.NewTestSink()
|
||||
|
||||
jobRetry := jobs.NewDeliveryRetryJob(app)
|
||||
app.Init(conf, ginengine, nc, []logic.Job{jobRetry})
|
||||
|
||||
router.Init(ginengine)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func requestGet[T any](t *testing.T, baseURL string, prefix string) T {
|
||||
client := http.Client{}
|
||||
|
||||
req, err := http.NewRequest("GET", baseURL+prefix, bytes.NewReader([]byte{}))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return *new(T)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return *new(T)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
t.Error("Statuscode != 200")
|
||||
}
|
||||
|
||||
respBodyBin, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return *new(T)
|
||||
}
|
||||
|
||||
var data T
|
||||
if err := json.Unmarshal(respBodyBin, &data); err != nil {
|
||||
t.Error(err)
|
||||
return *new(T)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
25
server/test/webserver_test.go
Normal file
25
server/test/webserver_test.go
Normal file
@ -0,0 +1,25 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWebserver(t *testing.T) {
|
||||
ws := NewSimpleWebserver(t)
|
||||
defer ws.Stop()
|
||||
go func() { ws.Run() }()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestPing(t *testing.T) {
|
||||
ws := NewSimpleWebserver(t)
|
||||
defer ws.Stop()
|
||||
go func() { ws.Run() }()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
baseUrl := "http://127.0.0.1:" + ws.Port
|
||||
|
||||
_ = requestGet[struct{}](t, baseUrl, "/api/ping")
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user