diff --git a/server/README.md b/server/README.md index a85da0c..43f4175 100644 --- a/server/README.md +++ b/server/README.md @@ -1,23 +1,30 @@ -//TODO + TODO +======== - - return subscribtions in list-channels (?) +------------------------------------------------------------------------------------------------------------------------------- - migration script for existing data - - ack/read deliveries && return ack-count (? or not, how to query?) - - - full-text-search: https://www.sqlite.org/fts5.html#contentless_tables - - app-store link in HTML - - route to re-check all pro-token + - route to re-check all pro-token (for me) - - tests + - tests (!) - deploy +------------------------------------------------------------------------------------------------------------------------------- + - in my script: use (backupname || hostname) for sendername - \ No newline at end of file +------------------------------------------------------------------------------------------------------------------------------- + + - (?) return subscribtions in list-channels (?) + + - (?) ack/read deliveries && return ack-count (? or not, how to query?) + + - (?) full-text-search: https://www.sqlite.org/fts5.html#contentless_tables + + - (?) "login" on website and list/search/filter messages \ No newline at end of file diff --git a/server/test/common_test.go b/server/test/common_test.go index ef6f75e..f7d92eb 100644 --- a/server/test/common_test.go +++ b/server/test/common_test.go @@ -20,12 +20,15 @@ import ( "net/http" "os" "path/filepath" + "runtime/debug" + "strings" + "testing" "time" ) type Void = struct{} -func NewSimpleWebserver() (*logic.Application, func()) { +func StartSimpleWebserver(t *testing.T) (*logic.Application, func()) { cw := zerolog.ConsoleWriter{ Out: os.Stdout, TimeFormat: "2006-01-02 15:04:05 Z07:00", @@ -43,28 +46,27 @@ func NewSimpleWebserver() (*logic.Application, func()) { gin.SetMode(gin.TestMode) zerolog.SetGlobalLevel(zerolog.DebugLevel) - uuid1, _ := langext.NewHexUUID() uuid2, _ := langext.NewHexUUID() - dbdir := filepath.Join(os.TempDir(), uuid1) + dbdir := t.TempDir() dbfile := filepath.Join(dbdir, uuid2+".sqlite3") err := os.MkdirAll(dbdir, os.ModePerm) if err != nil { - panic(err) + testFailErr(t, err) } f, err := os.Create(dbfile) if err != nil { - panic(err) + testFailErr(t, err) } err = f.Close() if err != nil { - panic(err) + testFailErr(t, err) } err = os.Chmod(dbfile, 0777) if err != nil { - panic(err) + testFailErr(t, err) } fmt.Println("DatabaseFile: " + dbfile) @@ -82,13 +84,13 @@ func NewSimpleWebserver() (*logic.Application, func()) { sqlite, err := db.NewDatabase(dbfile) if err != nil { - panic(err) + testFailErr(t, err) } app := logic.NewApp(sqlite) if err := app.Migrate(); err != nil { - panic(err) + testFailErr(t, err) } ginengine := ginext.NewEngine(conf) @@ -104,64 +106,67 @@ func NewSimpleWebserver() (*logic.Application, func()) { router.Init(ginengine) - return app, func() { app.Stop(); _ = os.Remove(dbfile) } + stop := func() { app.Stop(); _ = os.Remove(dbfile) } + go func() { app.Run() }() + time.Sleep(100 * time.Millisecond) + return app, stop } -func requestGet[TResult any](baseURL string, prefix string) TResult { - return requestAny[TResult]("", "GET", baseURL, prefix, nil) +func requestGet[TResult any](t *testing.T, baseURL string, prefix string) TResult { + return requestAny[TResult](t, "", "GET", baseURL, prefix, nil) } -func requestAuthGet[TResult any](akey string, baseURL string, prefix string) TResult { - return requestAny[TResult](akey, "GET", baseURL, prefix, nil) +func requestAuthGet[TResult any](t *testing.T, akey string, baseURL string, prefix string) TResult { + return requestAny[TResult](t, akey, "GET", baseURL, prefix, nil) } -func requestPost[TResult any](baseURL string, prefix string, body any) TResult { - return requestAny[TResult]("", "POST", baseURL, prefix, body) +func requestPost[TResult any](t *testing.T, baseURL string, prefix string, body any) TResult { + return requestAny[TResult](t, "", "POST", baseURL, prefix, body) } -func requestAuthPost[TResult any](akey string, baseURL string, prefix string, body any) TResult { - return requestAny[TResult](akey, "POST", baseURL, prefix, body) +func requestAuthPost[TResult any](t *testing.T, akey string, baseURL string, prefix string, body any) TResult { + return requestAny[TResult](t, akey, "POST", baseURL, prefix, body) } -func requestPut[TResult any](baseURL string, prefix string, body any) TResult { - return requestAny[TResult]("", "PUT", baseURL, prefix, body) +func requestPut[TResult any](t *testing.T, baseURL string, prefix string, body any) TResult { + return requestAny[TResult](t, "", "PUT", baseURL, prefix, body) } -func requestAuthPUT[TResult any](akey string, baseURL string, prefix string, body any) TResult { - return requestAny[TResult](akey, "PUT", baseURL, prefix, body) +func requestAuthPUT[TResult any](t *testing.T, akey string, baseURL string, prefix string, body any) TResult { + return requestAny[TResult](t, akey, "PUT", baseURL, prefix, body) } -func requestPatch[TResult any](baseURL string, prefix string, body any) TResult { - return requestAny[TResult]("", "PATCH", baseURL, prefix, body) +func requestPatch[TResult any](t *testing.T, baseURL string, prefix string, body any) TResult { + return requestAny[TResult](t, "", "PATCH", baseURL, prefix, body) } -func requestAuthPatch[TResult any](akey string, baseURL string, prefix string, body any) TResult { - return requestAny[TResult](akey, "PATCH", baseURL, prefix, body) +func requestAuthPatch[TResult any](t *testing.T, akey string, baseURL string, prefix string, body any) TResult { + return requestAny[TResult](t, akey, "PATCH", baseURL, prefix, body) } -func requestDelete[TResult any](baseURL string, prefix string, body any) TResult { - return requestAny[TResult]("", "DELETE", baseURL, prefix, body) +func requestDelete[TResult any](t *testing.T, baseURL string, prefix string, body any) TResult { + return requestAny[TResult](t, "", "DELETE", baseURL, prefix, body) } -func requestAuthDelete[TResult any](akey string, baseURL string, prefix string, body any) TResult { - return requestAny[TResult](akey, "DELETE", baseURL, prefix, body) +func requestAuthDelete[TResult any](t *testing.T, akey string, baseURL string, prefix string, body any) TResult { + return requestAny[TResult](t, akey, "DELETE", baseURL, prefix, body) } -func requestAny[TResult any](akey string, method string, baseURL string, prefix string, body any) TResult { +func requestAny[TResult any](t *testing.T, akey string, method string, baseURL string, prefix string, body any) TResult { client := http.Client{} bytesbody := make([]byte, 0) if body != nil { bjson, err := json.Marshal(body) if err != nil { - panic(err) + testFailErr(t, err) } bytesbody = bjson } req, err := http.NewRequest(method, baseURL+prefix, bytes.NewReader(bytesbody)) if err != nil { - panic(err) + testFailErr(t, err) } if body != nil { @@ -174,25 +179,63 @@ func requestAny[TResult any](akey string, method string, baseURL string, prefix resp, err := client.Do(req) if err != nil { - panic(err) + testFailErr(t, err) } defer func() { _ = resp.Body.Close() }() respBodyBin, err := io.ReadAll(resp.Body) if err != nil { - panic(err) + testFailErr(t, err) } if resp.StatusCode != 200 { fmt.Println("Request: " + method + " :: " + baseURL + prefix) fmt.Println(string(respBodyBin)) - panic("Statuscode != 200") + testFail(t, "Statuscode != 200") } var data TResult if err := json.Unmarshal(respBodyBin, &data); err != nil { - panic(err) + testFailErr(t, err) } return data } + +func assertEqual(t *testing.T, key string, expected any, actual any) { + if expected != actual { + t.Errorf("Value [%s] differs (%T <-> %T):\n", key, expected, actual) + + str1 := fmt.Sprintf("%v", expected) + str2 := fmt.Sprintf("%v", actual) + + if strings.Contains(str1, "\n") { + t.Errorf("Actual :\n~~~~~~~~~~~~~~~~\n%v\n~~~~~~~~~~~~~~~~\n\n", expected) + } else { + t.Errorf("Actual : \"%v\"\n", expected) + } + + if strings.Contains(str2, "\n") { + t.Errorf("Expected :\n~~~~~~~~~~~~~~~~\n%v\n~~~~~~~~~~~~~~~~\n\n", actual) + } else { + t.Errorf("Expected : \"%v\"\n", actual) + } + + t.FailNow() + } +} + +func testFail(t *testing.T, msg string) { + t.Error(msg) + t.FailNow() +} + +func testFailFmt(t *testing.T, format string, args ...any) { + t.Errorf(format, args...) + t.FailNow() +} + +func testFailErr(t *testing.T, e error) { + t.Error(fmt.Sprintf("Failed with error:\n%s\n\nError:\n%+v\n\nTrace:\n%s", e.Error(), e, string(debug.Stack()))) + t.FailNow() +} diff --git a/server/test/user_test.go b/server/test/user_test.go index d223246..5bb0c6f 100644 --- a/server/test/user_test.go +++ b/server/test/user_test.go @@ -4,26 +4,102 @@ import ( "fmt" "github.com/gin-gonic/gin" "testing" - "time" ) func TestCreateUserNoClient(t *testing.T) { - ws, stop := NewSimpleWebserver() + ws, stop := StartSimpleWebserver(t) defer stop() - go func() { ws.Run() }() - time.Sleep(100 * time.Millisecond) baseUrl := "http://127.0.0.1:" + ws.Port - res := requestPost[gin.H](baseUrl, "/api/users", gin.H{ + r0 := requestPost[gin.H](t, baseUrl, "/api/users", gin.H{ "no_client": true, }) - uid := fmt.Sprintf("%v", res["user_id"]) - admintok := res["admin_key"].(string) + assertEqual(t, "len(clients)", 0, len(r0["clients"].([]any))) + + uid := fmt.Sprintf("%v", r0["user_id"]) + admintok := r0["admin_key"].(string) fmt.Printf("uid := %s\n", uid) fmt.Printf("admin_key := %s\n", admintok) - requestAuthGet[Void](admintok, baseUrl, "/api/users/"+uid) + r1 := requestAuthGet[gin.H](t, admintok, baseUrl, "/api/users/"+uid) + + assertEqual(t, "uid", uid, fmt.Sprintf("%v", r1["user_id"])) + assertEqual(t, "admin_key", admintok, r1["admin_key"]) +} + +func TestCreateUserDummyClient(t *testing.T) { + ws, stop := StartSimpleWebserver(t) + defer stop() + + baseUrl := "http://127.0.0.1:" + ws.Port + + r0 := requestPost[gin.H](t, baseUrl, "/api/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + }) + + uid := fmt.Sprintf("%v", r0["user_id"]) + + assertEqual(t, "len(clients)", 1, len(r0["clients"].([]any))) + + admintok := r0["admin_key"].(string) + + fmt.Printf("uid := %s\n", uid) + fmt.Printf("admin_key := %s\n", admintok) + + r1 := requestAuthGet[gin.H](t, admintok, baseUrl, "/api/users/"+uid) + + assertEqual(t, "uid", uid, fmt.Sprintf("%v", r1["user_id"])) + assertEqual(t, "admin_key", admintok, r1["admin_key"]) + assertEqual(t, "username", nil, r1["username"]) + + type rt2 struct { + Clients []gin.H `json:"clients"` + } + + r2 := requestAuthGet[rt2](t, admintok, baseUrl, "/api/users/"+uid+"/clients") + + assertEqual(t, "len(clients)", 1, len(r2.Clients)) + + c0 := r2.Clients[0] + + assertEqual(t, "agent_model", "DUMMY_PHONE", c0["agent_model"]) + assertEqual(t, "agent_version", "4X", c0["agent_version"]) + assertEqual(t, "fcm_token", "DUMMY_FCM", c0["fcm_token"]) + assertEqual(t, "client_type", "ANDROID", c0["type"]) +} + +func TestCreateUserWithUsername(t *testing.T) { + ws, stop := StartSimpleWebserver(t) + defer stop() + + baseUrl := "http://127.0.0.1:" + ws.Port + + r0 := requestPost[gin.H](t, baseUrl, "/api/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + "username": "my_user", + }) + + assertEqual(t, "len(clients)", 1, len(r0["clients"].([]any))) + + uid := fmt.Sprintf("%v", r0["user_id"]) + + admintok := r0["admin_key"].(string) + + fmt.Printf("uid := %s\n", uid) + fmt.Printf("admin_key := %s\n", admintok) + + r1 := requestAuthGet[gin.H](t, admintok, baseUrl, "/api/users/"+uid) + + assertEqual(t, "uid", uid, fmt.Sprintf("%v", r1["user_id"])) + assertEqual(t, "admin_key", admintok, r1["admin_key"]) + assertEqual(t, "username", "my_user", r1["username"]) } diff --git a/server/test/webserver_test.go b/server/test/webserver_test.go index f2d6440..61fc267 100644 --- a/server/test/webserver_test.go +++ b/server/test/webserver_test.go @@ -1,50 +1,44 @@ package test import ( + "fmt" "testing" - "time" ) func TestWebserver(t *testing.T) { - ws, stop := NewSimpleWebserver() + ws, stop := StartSimpleWebserver(t) defer stop() - go func() { ws.Run() }() - time.Sleep(100 * time.Millisecond) + + fmt.Printf("Port := %s\n", ws.Port) } func TestPing(t *testing.T) { - ws, stop := NewSimpleWebserver() + ws, stop := StartSimpleWebserver(t) defer stop() - go func() { ws.Run() }() - time.Sleep(100 * time.Millisecond) baseUrl := "http://127.0.0.1:" + ws.Port - _ = requestGet[Void](baseUrl, "/api/ping") - _ = requestPut[Void](baseUrl, "/api/ping", nil) - _ = requestPost[Void](baseUrl, "/api/ping", nil) - _ = requestPatch[Void](baseUrl, "/api/ping", nil) - _ = requestDelete[Void](baseUrl, "/api/ping", nil) + _ = requestGet[Void](t, baseUrl, "/api/ping") + _ = requestPut[Void](t, baseUrl, "/api/ping", nil) + _ = requestPost[Void](t, baseUrl, "/api/ping", nil) + _ = requestPatch[Void](t, baseUrl, "/api/ping", nil) + _ = requestDelete[Void](t, baseUrl, "/api/ping", nil) } func TestMongo(t *testing.T) { - ws, stop := NewSimpleWebserver() + ws, stop := StartSimpleWebserver(t) defer stop() - go func() { ws.Run() }() - time.Sleep(100 * time.Millisecond) baseUrl := "http://127.0.0.1:" + ws.Port - _ = requestPost[Void](baseUrl, "/api/db-test", nil) + _ = requestPost[Void](t, baseUrl, "/api/db-test", nil) } func TestHealth(t *testing.T) { - ws, stop := NewSimpleWebserver() + ws, stop := StartSimpleWebserver(t) defer stop() - go func() { ws.Run() }() - time.Sleep(100 * time.Millisecond) baseUrl := "http://127.0.0.1:" + ws.Port - _ = requestGet[Void](baseUrl, "/api/health") + _ = requestGet[Void](t, baseUrl, "/api/health") }