Refactor server to go-sqlite and ginext [WIP]
This commit is contained in:
parent
c204dc5a8b
commit
ca05d6e3cc
144
scnserver/test/response_test.go
Normal file
144
scnserver/test/response_test.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
tt "blackforestbytes.com/simplecloudnotifier/test/util"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResponseChannel(t *testing.T) {
|
||||||
|
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
data := tt.InitDefaultData(t, ws)
|
||||||
|
|
||||||
|
response := tt.RequestAuthGetRaw(t, data.User[0].AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels/%s", data.User[0].UID, data.User[0].Channels[0]))
|
||||||
|
|
||||||
|
tt.AssertJsonStructureMatch(t, "json[channel]", response, map[string]any{
|
||||||
|
"channel_id": "id",
|
||||||
|
"owner_user_id": "id",
|
||||||
|
"internal_name": "string",
|
||||||
|
"display_name": "string",
|
||||||
|
"description_name": "null",
|
||||||
|
"subscribe_key": "string",
|
||||||
|
"timestamp_created": "rfc3339",
|
||||||
|
"timestamp_lastsent": "rfc3339",
|
||||||
|
"messages_sent": "int",
|
||||||
|
"subscription": map[string]any{
|
||||||
|
"subscription_id": "id",
|
||||||
|
"subscriber_user_id": "id",
|
||||||
|
"channel_owner_user_id": "id",
|
||||||
|
"channel_id": "id",
|
||||||
|
"channel_internal_name": "string",
|
||||||
|
"timestamp_created": "rfc3339",
|
||||||
|
"confirmed": "bool",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResponseClient(t *testing.T) {
|
||||||
|
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
data := tt.InitDefaultData(t, ws)
|
||||||
|
|
||||||
|
response := tt.RequestAuthGetRaw(t, data.User[0].AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/clients/%s", data.User[0].UID, data.User[0].Clients[0]))
|
||||||
|
|
||||||
|
tt.AssertJsonStructureMatch(t, "json[client]", response, map[string]any{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResponseKeyToken(t *testing.T) {
|
||||||
|
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
data := tt.InitDefaultData(t, ws)
|
||||||
|
|
||||||
|
response := tt.RequestAuthGetRaw(t, data.User[0].AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/%s", data.User[0].UID, data.User[0].Keys[0]))
|
||||||
|
|
||||||
|
tt.AssertJsonStructureMatch(t, "json[key]", response, map[string]any{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResponseMessage(t *testing.T) {
|
||||||
|
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
data := tt.InitDefaultData(t, ws)
|
||||||
|
|
||||||
|
response := tt.RequestAuthGetRaw(t, data.User[0].AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/messages/%s", data.User[0].UID, data.User[0].Messages[0]))
|
||||||
|
|
||||||
|
tt.AssertJsonStructureMatch(t, "json[message]", response, map[string]any{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResponseSubscription(t *testing.T) {
|
||||||
|
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
data := tt.InitDefaultData(t, ws)
|
||||||
|
|
||||||
|
response := tt.RequestAuthGetRaw(t, data.User[0].AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/subscriptions/%s", data.User[0].UID, data.User[0].Subscriptions[0]))
|
||||||
|
|
||||||
|
tt.AssertJsonStructureMatch(t, "json[subscription]", response, map[string]any{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResponseUser(t *testing.T) {
|
||||||
|
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
data := tt.InitDefaultData(t, ws)
|
||||||
|
|
||||||
|
response := tt.RequestAuthGetRaw(t, data.User[0].AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s", data.User[0].UID))
|
||||||
|
|
||||||
|
tt.AssertJsonStructureMatch(t, "json[user]", response, map[string]any{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResponseChannelPreview(t *testing.T) {
|
||||||
|
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
data := tt.InitDefaultData(t, ws)
|
||||||
|
|
||||||
|
response := tt.RequestAuthGetRaw(t, data.User[1].AdminKey, baseUrl, fmt.Sprintf("/api/v2/preview/channels/%s", data.User[0].Channels[0]))
|
||||||
|
|
||||||
|
tt.AssertJsonStructureMatch(t, "json[channel]", response, map[string]any{
|
||||||
|
"channel_id": "id",
|
||||||
|
"owner_user_id": "id",
|
||||||
|
"internal_name": "string",
|
||||||
|
"display_name": "string",
|
||||||
|
"description_name": "null",
|
||||||
|
"subscribe_key": "string",
|
||||||
|
"timestamp_created": "rfc3339",
|
||||||
|
"timestamp_lastsent": "rfc3339",
|
||||||
|
"messages_sent": "int",
|
||||||
|
"subscription": map[string]any{
|
||||||
|
"subscription_id": "id",
|
||||||
|
"subscriber_user_id": "id",
|
||||||
|
"channel_owner_user_id": "id",
|
||||||
|
"channel_id": "id",
|
||||||
|
"channel_internal_name": "string",
|
||||||
|
"timestamp_created": "rfc3339",
|
||||||
|
"confirmed": "bool",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResponseUserPreview(t *testing.T) {
|
||||||
|
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
data := tt.InitDefaultData(t, ws)
|
||||||
|
|
||||||
|
response := tt.RequestAuthGetRaw(t, data.User[1].AdminKey, baseUrl, fmt.Sprintf("/api/v2/preview/users/%s", data.User[0].UID))
|
||||||
|
|
||||||
|
tt.AssertJsonStructureMatch(t, "json[user]", response, map[string]any{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResponseKeyTokenPreview(t *testing.T) {
|
||||||
|
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
data := tt.InitDefaultData(t, ws)
|
||||||
|
|
||||||
|
response := tt.RequestAuthGetRaw(t, data.User[1].AdminKey, baseUrl, fmt.Sprintf("/api/v2/preview/keys/%s", data.User[0].Keys[0]))
|
||||||
|
|
||||||
|
tt.AssertJsonStructureMatch(t, "json[key]", response, map[string]any{})
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
|
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
|
||||||
"gopkg.in/loremipsum.v1"
|
"gopkg.in/loremipsum.v1"
|
||||||
"testing"
|
"testing"
|
||||||
@ -63,6 +64,11 @@ type Userdat struct {
|
|||||||
SendKey string
|
SendKey string
|
||||||
AdminKey string
|
AdminKey string
|
||||||
ReadKey string
|
ReadKey string
|
||||||
|
Clients []string
|
||||||
|
Channels []string
|
||||||
|
Messages []string
|
||||||
|
Keys []string
|
||||||
|
Subscriptions []string
|
||||||
}
|
}
|
||||||
|
|
||||||
const PX = -1
|
const PX = -1
|
||||||
@ -367,7 +373,8 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
|
|||||||
body["agent_version"] = cex.AgentVersion
|
body["agent_version"] = cex.AgentVersion
|
||||||
body["client_type"] = cex.ClientType
|
body["client_type"] = cex.ClientType
|
||||||
body["fcm_token"] = cex.FCMTok
|
body["fcm_token"] = cex.FCMTok
|
||||||
RequestAuthPost[gin.H](t, users[cex.User].AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/clients", users[cex.User].UID), body)
|
r0 := RequestAuthPost[gin.H](t, users[cex.User].AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/clients", users[cex.User].UID), body)
|
||||||
|
users[cex.User].Clients = append(users[cex.User].Clients, r0["client_id"].(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Messages
|
// Create Messages
|
||||||
@ -398,7 +405,8 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
|
|||||||
body["timestamp"] = (time.Now().Add(mex.TSOffset)).Unix()
|
body["timestamp"] = (time.Now().Add(mex.TSOffset)).Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestPost[gin.H](t, baseUrl, "/", body)
|
r0 := RequestPost[gin.H](t, baseUrl, "/", body)
|
||||||
|
users[mex.User].Messages = append(users[mex.User].Messages, r0["scn_msg_id"].(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
// create manual channels
|
// create manual channels
|
||||||
@ -407,6 +415,45 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
|
|||||||
RequestAuthPost[Void](t, users[9].AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels", users[9].UID), gin.H{"name": "manual@chan"})
|
RequestAuthPost[Void](t, users[9].AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels", users[9].UID), gin.H{"name": "manual@chan"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// list channels
|
||||||
|
|
||||||
|
for i, usr := range users {
|
||||||
|
type schan struct {
|
||||||
|
ID string `json:"channel_id"`
|
||||||
|
}
|
||||||
|
type chanlist struct {
|
||||||
|
Channels []schan `json:"channels"`
|
||||||
|
}
|
||||||
|
r0 := RequestAuthGet[chanlist](t, usr.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels?selector=%s", usr.UID, "owned"))
|
||||||
|
users[i].Channels = langext.ArrMap(r0.Channels, func(v schan) string { return v.ID })
|
||||||
|
}
|
||||||
|
|
||||||
|
// list keys
|
||||||
|
|
||||||
|
for i, usr := range users {
|
||||||
|
type skey struct {
|
||||||
|
ID string `json:"keytoken_id"`
|
||||||
|
}
|
||||||
|
type keylist struct {
|
||||||
|
Keys []skey `json:"channels"`
|
||||||
|
}
|
||||||
|
r0 := RequestAuthGet[keylist](t, usr.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys", usr.UID))
|
||||||
|
users[i].Keys = langext.ArrMap(r0.Keys, func(v skey) string { return v.ID })
|
||||||
|
}
|
||||||
|
|
||||||
|
// list subscriptions
|
||||||
|
|
||||||
|
for i, usr := range users {
|
||||||
|
type ssub struct {
|
||||||
|
ID string `json:"subscription_id"`
|
||||||
|
}
|
||||||
|
type sublist struct {
|
||||||
|
Subs []ssub `json:"channels"`
|
||||||
|
}
|
||||||
|
r0 := RequestAuthGet[sublist](t, usr.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/subscriptions?direction=%s&confirmation=%s", usr.UID, "outgoing", "confirmed"))
|
||||||
|
users[i].Keys = langext.ArrMap(r0.Subs, func(v ssub) string { return v.ID })
|
||||||
|
}
|
||||||
|
|
||||||
// Sub/Unsub for Users 12+13
|
// Sub/Unsub for Users 12+13
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -474,7 +521,7 @@ func InitSingleData(t *testing.T, ws *logic.Application) SingleData {
|
|||||||
|
|
||||||
func doSubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat, chanInternalName string) {
|
func doSubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat, chanInternalName string) {
|
||||||
|
|
||||||
if user == chanOwner {
|
if user.UID == chanOwner.UID {
|
||||||
|
|
||||||
RequestAuthPost[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels", user.UID), gin.H{
|
RequestAuthPost[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels", user.UID), gin.H{
|
||||||
"channel_owner_user_id": chanOwner.UID,
|
"channel_owner_user_id": chanOwner.UID,
|
||||||
|
@ -26,6 +26,10 @@ func RequestAuthGet[TResult any](t *testing.T, akey string, baseURL string, urlS
|
|||||||
return RequestAny[TResult](t, akey, "GET", baseURL, urlSuffix, nil, true)
|
return RequestAny[TResult](t, akey, "GET", baseURL, urlSuffix, nil, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RequestAuthGetRaw(t *testing.T, akey string, baseURL string, urlSuffix string) string {
|
||||||
|
return RequestAny[string](t, akey, "GET", baseURL, urlSuffix, nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
func RequestPost[TResult any](t *testing.T, baseURL string, urlSuffix string, body any) TResult {
|
func RequestPost[TResult any](t *testing.T, baseURL string, urlSuffix string, body any) TResult {
|
||||||
return RequestAny[TResult](t, "", "POST", baseURL, urlSuffix, body, true)
|
return RequestAny[TResult](t, "", "POST", baseURL, urlSuffix, body, true)
|
||||||
}
|
}
|
||||||
@ -166,14 +170,22 @@ func RequestAny[TResult any](t *testing.T, akey string, method string, baseURL s
|
|||||||
TestFailFmt(t, "Statuscode != 200 (actual = %d)", resp.StatusCode)
|
TestFailFmt(t, "Statuscode != 200 (actual = %d)", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
var data TResult
|
|
||||||
if deserialize {
|
if deserialize {
|
||||||
|
var data TResult
|
||||||
if err := json.Unmarshal(respBodyBin, &data); err != nil {
|
if err := json.Unmarshal(respBodyBin, &data); err != nil {
|
||||||
TestFailErr(t, err)
|
TestFailErr(t, err)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
} else {
|
||||||
|
if _, ok := (any(*new(TResult))).([]byte); ok {
|
||||||
|
return any(respBodyBin).(TResult)
|
||||||
|
} else if _, ok := (any(*new(TResult))).(string); ok {
|
||||||
|
return any(string(respBodyBin)).(TResult)
|
||||||
|
} else {
|
||||||
|
return *new(TResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RequestAuthAnyShouldFail(t *testing.T, akey string, method string, baseURL string, urlSuffix string, body any, expectedStatusCode int, errcode apierr.APIError) {
|
func RequestAuthAnyShouldFail(t *testing.T, akey string, method string, baseURL string, urlSuffix string, body any, expectedStatusCode int, errcode apierr.APIError) {
|
||||||
|
118
scnserver/test/util/structure.go
Normal file
118
scnserver/test/util/structure.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AssertJsonStructureMatch(t *testing.T, key string, jsonData string, expected map[string]any) {
|
||||||
|
|
||||||
|
realData := make(map[string]any)
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(jsonData), &realData)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to decode json of [%s]: %s", key, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
AssertJsonStructureMatchOfMap(t, key, realData, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssertJsonStructureMatchOfMap(t *testing.T, key string, realData map[string]any, expected map[string]any) {
|
||||||
|
|
||||||
|
for k := range expected {
|
||||||
|
if _, ok := realData[k]; !ok {
|
||||||
|
t.Errorf("Missing Key in data '%s': [[%s]]", key, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range realData {
|
||||||
|
if _, ok := expected[k]; !ok {
|
||||||
|
t.Errorf("Additional key in data '%s': [[%s]]", key, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range realData {
|
||||||
|
|
||||||
|
schema, ok := expected[k]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strschema, ok := schema.(string); ok {
|
||||||
|
switch strschema {
|
||||||
|
case "id":
|
||||||
|
if _, ok := v.(string); !ok {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a string<id> (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(v.(string)) != 24 { //TODO validate checksum?
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a valid entity-id date (its '%v')", k, key, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "string":
|
||||||
|
if _, ok := v.(string); !ok {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a string (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "null":
|
||||||
|
if !langext.IsNil(v) {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a NULL (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "rfc3339":
|
||||||
|
if _, ok := v.(string); !ok {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a string<rfc3339> (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := time.Parse(time.RFC3339, v.(string)); err != nil {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a valid rfc3339 date (its '%v')", k, key, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "int":
|
||||||
|
if _, ok := v.(float64); !ok {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a int (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v.(float64) != float64(int(v.(float64))) {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a int (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "float":
|
||||||
|
if _, ok := v.(float64); !ok {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a int (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "bool":
|
||||||
|
if _, ok := v.(bool); !ok {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a int (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "object":
|
||||||
|
if reflect.ValueOf(v).Kind() != reflect.Map {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a object (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "array":
|
||||||
|
if reflect.ValueOf(v).Kind() != reflect.Array {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a array (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if mapschema, ok := schema.(map[string]any); ok {
|
||||||
|
if reflect.ValueOf(v).Kind() != reflect.Map {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a object (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := v.(map[string]any); !ok {
|
||||||
|
t.Errorf("Key [[%s]] in data '%s' is not a object[recursive] (its actually %T: '%v')", k, key, v, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
AssertJsonStructureMatchOfMap(t, key+".["+k+"]", v.(map[string]any), mapschema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user