Merge branch 'test/remove_userid_param'

This commit is contained in:
2025-12-05 14:30:55 +01:00
16 changed files with 138 additions and 191 deletions

View File

@@ -1,6 +1,11 @@
package handler package handler
import ( import (
"database/sql"
"errors"
"fmt"
"net/http"
"blackforestbytes.com/simplecloudnotifier/api/apierr" "blackforestbytes.com/simplecloudnotifier/api/apierr"
hl "blackforestbytes.com/simplecloudnotifier/api/apihighlight" hl "blackforestbytes.com/simplecloudnotifier/api/apihighlight"
"blackforestbytes.com/simplecloudnotifier/api/ginresp" "blackforestbytes.com/simplecloudnotifier/api/ginresp"
@@ -8,13 +13,9 @@ import (
primarydb "blackforestbytes.com/simplecloudnotifier/db/impl/primary" primarydb "blackforestbytes.com/simplecloudnotifier/db/impl/primary"
"blackforestbytes.com/simplecloudnotifier/logic" "blackforestbytes.com/simplecloudnotifier/logic"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"database/sql"
"errors"
"fmt"
"git.blackforestbytes.com/BlackForestBytes/goext/dataext" "git.blackforestbytes.com/BlackForestBytes/goext/dataext"
"git.blackforestbytes.com/BlackForestBytes/goext/ginext" "git.blackforestbytes.com/BlackForestBytes/goext/ginext"
"git.blackforestbytes.com/BlackForestBytes/goext/langext" "git.blackforestbytes.com/BlackForestBytes/goext/langext"
"net/http"
) )
type CompatHandler struct { type CompatHandler struct {
@@ -90,7 +91,7 @@ func (h CompatHandler) SendMessage(pctx ginext.PreContext) ginext.HTTPResponse {
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found (compat)", nil) return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found (compat)", nil)
} }
okResp, errResp := h.app.SendMessage(g, ctx, langext.Ptr(models.UserID(*newid)), data.UserKey, nil, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp, nil) okResp, errResp := h.app.SendMessage(g, ctx, data.UserKey, nil, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp, nil)
if errResp != nil { if errResp != nil {
return *errResp return *errResp
} else { } else {

View File

@@ -1,16 +1,17 @@
package handler package handler
import ( import (
"fmt"
"net/http"
"time"
"blackforestbytes.com/simplecloudnotifier/api/apierr" "blackforestbytes.com/simplecloudnotifier/api/apierr"
"blackforestbytes.com/simplecloudnotifier/api/ginresp" "blackforestbytes.com/simplecloudnotifier/api/ginresp"
primarydb "blackforestbytes.com/simplecloudnotifier/db/impl/primary" primarydb "blackforestbytes.com/simplecloudnotifier/db/impl/primary"
"blackforestbytes.com/simplecloudnotifier/logic" "blackforestbytes.com/simplecloudnotifier/logic"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"fmt"
"git.blackforestbytes.com/BlackForestBytes/goext/ginext" "git.blackforestbytes.com/BlackForestBytes/goext/ginext"
"git.blackforestbytes.com/BlackForestBytes/goext/langext" "git.blackforestbytes.com/BlackForestBytes/goext/langext"
"net/http"
"time"
) )
type ExternalHandler struct { type ExternalHandler struct {
@@ -36,22 +37,21 @@ func NewExternalHandler(app *logic.Application) ExternalHandler {
// //
// @Success 200 {object} handler.UptimeKuma.response // @Success 200 {object} handler.UptimeKuma.response
// @Failure 400 {object} ginresp.apiError // @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError "The user_id was not found or the user_key is wrong" // @Failure 401 {object} ginresp.apiError "The user_key is wrong"
// @Failure 403 {object} ginresp.apiError "The user has exceeded its daily quota - wait 24 hours or upgrade your account" // @Failure 403 {object} ginresp.apiError "The user has exceeded its daily quota - wait 24 hours or upgrade your account"
// @Failure 500 {object} ginresp.apiError "An internal server error occurred - try again later" // @Failure 500 {object} ginresp.apiError "An internal server error occurred - try again later"
// //
// @Router /external/v1/uptime-kuma [POST] // @Router /external/v1/uptime-kuma [POST]
func (h ExternalHandler) UptimeKuma(pctx ginext.PreContext) ginext.HTTPResponse { func (h ExternalHandler) UptimeKuma(pctx ginext.PreContext) ginext.HTTPResponse {
type query struct { type query struct {
UserID *models.UserID `form:"user_id" example:"7725"` KeyToken *string `form:"key" example:"P3TNH8mvv14fm"`
KeyToken *string `form:"key" example:"P3TNH8mvv14fm"` Channel *string `form:"channel"`
Channel *string `form:"channel"` ChannelUp *string `form:"channel_up"`
ChannelUp *string `form:"channel_up"` ChannelDown *string `form:"channel_down"`
ChannelDown *string `form:"channel_down"` Priority *int `form:"priority"`
Priority *int `form:"priority"` PriorityUp *int `form:"priority_up"`
PriorityUp *int `form:"priority_up"` PriorityDown *int `form:"priority_down"`
PriorityDown *int `form:"priority_down"` SenderName *string `form:"senderName"`
SenderName *string `form:"senderName"`
} }
type body struct { type body struct {
Heartbeat *struct { Heartbeat *struct {
@@ -125,7 +125,7 @@ func (h ExternalHandler) UptimeKuma(pctx ginext.PreContext) ginext.HTTPResponse
priority = q.PriorityDown priority = q.PriorityDown
} }
okResp, errResp := h.app.SendMessage(g, ctx, q.UserID, q.KeyToken, channel, &title, &content, priority, nil, timestamp, q.SenderName) okResp, errResp := h.app.SendMessage(g, ctx, q.KeyToken, channel, &title, &content, priority, nil, timestamp, q.SenderName)
if errResp != nil { if errResp != nil {
return *errResp return *errResp
} }

View File

@@ -1,6 +1,8 @@
package handler package handler
import ( import (
"net/http"
"blackforestbytes.com/simplecloudnotifier/api/apierr" "blackforestbytes.com/simplecloudnotifier/api/apierr"
primarydb "blackforestbytes.com/simplecloudnotifier/db/impl/primary" primarydb "blackforestbytes.com/simplecloudnotifier/db/impl/primary"
"blackforestbytes.com/simplecloudnotifier/logic" "blackforestbytes.com/simplecloudnotifier/logic"
@@ -8,7 +10,6 @@ import (
"git.blackforestbytes.com/BlackForestBytes/goext/dataext" "git.blackforestbytes.com/BlackForestBytes/goext/dataext"
"git.blackforestbytes.com/BlackForestBytes/goext/ginext" "git.blackforestbytes.com/BlackForestBytes/goext/ginext"
"git.blackforestbytes.com/BlackForestBytes/goext/langext" "git.blackforestbytes.com/BlackForestBytes/goext/langext"
"net/http"
) )
type SendMessageResponse struct { type SendMessageResponse struct {
@@ -42,7 +43,7 @@ func NewMessageHandler(app *logic.Application) MessageHandler {
// //
// @Success 200 {object} handler.SendMessage.response // @Success 200 {object} handler.SendMessage.response
// @Failure 400 {object} ginresp.apiError // @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError "The user_id was not found or the user_key is wrong" // @Failure 401 {object} ginresp.apiError "The user_key is wrong"
// @Failure 403 {object} ginresp.apiError "The user has exceeded its daily quota - wait 24 hours or upgrade your account" // @Failure 403 {object} ginresp.apiError "The user has exceeded its daily quota - wait 24 hours or upgrade your account"
// @Failure 500 {object} ginresp.apiError "An internal server error occurred - try again later" // @Failure 500 {object} ginresp.apiError "An internal server error occurred - try again later"
// //
@@ -50,15 +51,14 @@ func NewMessageHandler(app *logic.Application) MessageHandler {
// @Router /send [POST] // @Router /send [POST]
func (h MessageHandler) SendMessage(pctx ginext.PreContext) ginext.HTTPResponse { func (h MessageHandler) SendMessage(pctx ginext.PreContext) ginext.HTTPResponse {
type combined struct { type combined struct {
UserID *models.UserID `json:"user_id" form:"user_id" example:"7725" ` KeyToken *string `json:"key" form:"key" example:"P3TNH8mvv14fm" `
KeyToken *string `json:"key" form:"key" example:"P3TNH8mvv14fm" ` Channel *string `json:"channel" form:"channel" example:"test" `
Channel *string `json:"channel" form:"channel" example:"test" ` Title *string `json:"title" form:"title" example:"Hello World" `
Title *string `json:"title" form:"title" example:"Hello World" ` Content *string `json:"content" form:"content" example:"This is a message" `
Content *string `json:"content" form:"content" example:"This is a message" ` Priority *int `json:"priority" form:"priority" example:"1" enums:"0,1,2" `
Priority *int `json:"priority" form:"priority" example:"1" enums:"0,1,2" ` UserMessageID *string `json:"msg_id" form:"msg_id" example:"db8b0e6a-a08c-4646" `
UserMessageID *string `json:"msg_id" form:"msg_id" example:"db8b0e6a-a08c-4646" ` SendTimestamp *float64 `json:"timestamp" form:"timestamp" example:"1669824037" `
SendTimestamp *float64 `json:"timestamp" form:"timestamp" example:"1669824037" ` SenderName *string `json:"sender_name" form:"sender_name" example:"example-server" `
SenderName *string `json:"sender_name" form:"sender_name" example:"example-server" `
} }
type response struct { type response struct {
@@ -88,7 +88,7 @@ func (h MessageHandler) SendMessage(pctx ginext.PreContext) ginext.HTTPResponse
// query has highest prio, then form, then json // query has highest prio, then form, then json
data := dataext.ObjectMerge(dataext.ObjectMerge(b, f), q) data := dataext.ObjectMerge(dataext.ObjectMerge(b, f), q)
okResp, errResp := h.app.SendMessage(g, ctx, data.UserID, data.KeyToken, data.Channel, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp, data.SenderName) okResp, errResp := h.app.SendMessage(g, ctx, data.KeyToken, data.Channel, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp, data.SenderName)
if errResp != nil { if errResp != nil {
return *errResp return *errResp
} else { } else {

View File

@@ -1,12 +1,13 @@
package primary package primary
import ( import (
"time"
scn "blackforestbytes.com/simplecloudnotifier" scn "blackforestbytes.com/simplecloudnotifier"
"blackforestbytes.com/simplecloudnotifier/db" "blackforestbytes.com/simplecloudnotifier/db"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"git.blackforestbytes.com/BlackForestBytes/goext/langext" "git.blackforestbytes.com/BlackForestBytes/goext/langext"
"git.blackforestbytes.com/BlackForestBytes/goext/sq" "git.blackforestbytes.com/BlackForestBytes/goext/sq"
"time"
) )
func (db *Database) CreateUser(ctx db.TxContext, protoken *string, username *string) (models.User, error) { func (db *Database) CreateUser(ctx db.TxContext, protoken *string, username *string) (models.User, error) {
@@ -63,6 +64,15 @@ func (db *Database) GetUser(ctx db.TxContext, userid models.UserID) (models.User
return sq.QuerySingle[models.User](ctx, tx, "SELECT * FROM users WHERE user_id = :uid AND deleted=0 LIMIT 1", sq.PP{"uid": userid}, sq.SModeExtended, sq.Safe) return sq.QuerySingle[models.User](ctx, tx, "SELECT * FROM users WHERE user_id = :uid AND deleted=0 LIMIT 1", sq.PP{"uid": userid}, sq.SModeExtended, sq.Safe)
} }
func (db *Database) GetUserByKey(ctx db.TxContext, key string) (models.User, error) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return models.User{}, err
}
return sq.QuerySingle[models.User](ctx, tx, "SELECT * FROM users WHERE EXISTS(SELECT keytokens.keytoken_id FROM keytokens WHERE keytokens.token = :tok AND users.user_id = keytokens.owner_user_id AND keytokens.deleted=0) AND users.deleted=0 LIMIT 1", sq.PP{"tok": key}, sq.SModeExtended, sq.Safe)
}
func (db *Database) GetUserOpt(ctx db.TxContext, userid models.UserID) (*models.User, error) { func (db *Database) GetUserOpt(ctx db.TxContext, userid models.UserID) (*models.User, error) {
tx, err := ctx.GetOrCreateTransaction(db) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {

View File

@@ -1,21 +1,22 @@
package logic package logic
import ( import (
"database/sql"
"errors"
"fmt"
"strings"
"time"
"blackforestbytes.com/simplecloudnotifier/api/apierr" "blackforestbytes.com/simplecloudnotifier/api/apierr"
hl "blackforestbytes.com/simplecloudnotifier/api/apihighlight" hl "blackforestbytes.com/simplecloudnotifier/api/apihighlight"
"blackforestbytes.com/simplecloudnotifier/api/ginresp" "blackforestbytes.com/simplecloudnotifier/api/ginresp"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"database/sql"
"errors"
"fmt"
"git.blackforestbytes.com/BlackForestBytes/goext/ginext" "git.blackforestbytes.com/BlackForestBytes/goext/ginext"
"git.blackforestbytes.com/BlackForestBytes/goext/langext" "git.blackforestbytes.com/BlackForestBytes/goext/langext"
"git.blackforestbytes.com/BlackForestBytes/goext/mathext" "git.blackforestbytes.com/BlackForestBytes/goext/mathext"
"git.blackforestbytes.com/BlackForestBytes/goext/timeext" "git.blackforestbytes.com/BlackForestBytes/goext/timeext"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"strings"
"time"
) )
type SendMessageResponse struct { type SendMessageResponse struct {
@@ -25,7 +26,7 @@ type SendMessageResponse struct {
CompatMessageID int64 CompatMessageID int64
} }
func (app *Application) SendMessage(g *gin.Context, ctx *AppContext, UserID *models.UserID, Key *string, Channel *string, Title *string, Content *string, Priority *int, UserMessageID *string, SendTimestamp *float64, SenderName *string) (*SendMessageResponse, *ginext.HTTPResponse) { func (app *Application) SendMessage(g *gin.Context, ctx *AppContext, Key *string, Channel *string, Title *string, Content *string, Priority *int, UserMessageID *string, SendTimestamp *float64, SenderName *string) (*SendMessageResponse, *ginext.HTTPResponse) {
if Title != nil { if Title != nil {
Title = langext.Ptr(strings.TrimSpace(*Title)) Title = langext.Ptr(strings.TrimSpace(*Title))
} }
@@ -33,9 +34,6 @@ func (app *Application) SendMessage(g *gin.Context, ctx *AppContext, UserID *mod
UserMessageID = langext.Ptr(strings.TrimSpace(*UserMessageID)) UserMessageID = langext.Ptr(strings.TrimSpace(*UserMessageID))
} }
if UserID == nil {
return nil, langext.Ptr(ginresp.SendAPIError(g, 400, apierr.MISSING_UID, hl.USER_ID, "Missing parameter [[user_id]]", nil))
}
if Key == nil { if Key == nil {
return nil, langext.Ptr(ginresp.SendAPIError(g, 400, apierr.MISSING_TOK, hl.USER_KEY, "Missing parameter [[key]]", nil)) return nil, langext.Ptr(ginresp.SendAPIError(g, 400, apierr.MISSING_TOK, hl.USER_KEY, "Missing parameter [[key]]", nil))
} }
@@ -49,9 +47,9 @@ func (app *Application) SendMessage(g *gin.Context, ctx *AppContext, UserID *mod
return nil, langext.Ptr(ginresp.SendAPIError(g, 400, apierr.NO_TITLE, hl.TITLE, "No title specified", nil)) return nil, langext.Ptr(ginresp.SendAPIError(g, 400, apierr.NO_TITLE, hl.TITLE, "No title specified", nil))
} }
user, err := app.Database.Primary.GetUser(ctx, *UserID) user, err := app.Database.Primary.GetUserByKey(ctx, *Key)
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return nil, langext.Ptr(ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found", err)) return nil, langext.Ptr(ginresp.SendAPIError(g, 401, apierr.USER_AUTH_FAILED, hl.USER_KEY, "Key not found or not valid", err))
} }
if err != nil { if err != nil {
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query user", err)) return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query user", err))
@@ -126,7 +124,7 @@ func (app *Application) SendMessage(g *gin.Context, ctx *AppContext, UserID *mod
return nil, langext.Ptr(ginresp.SendAPIError(g, 403, apierr.QUOTA_REACHED, hl.NONE, fmt.Sprintf("Daily quota reached (%d)", user.QuotaPerDay()), nil)) return nil, langext.Ptr(ginresp.SendAPIError(g, 403, apierr.QUOTA_REACHED, hl.NONE, fmt.Sprintf("Daily quota reached (%d)", user.QuotaPerDay()), nil))
} }
channel, err := app.GetOrCreateChannel(ctx, *UserID, channelDisplayName, channelInternalName) channel, err := app.GetOrCreateChannel(ctx, user.UserID, channelDisplayName, channelInternalName)
if err != nil { if err != nil {
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query/create (owned) channel", err)) return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query/create (owned) channel", err))
} }
@@ -145,7 +143,7 @@ func (app *Application) SendMessage(g *gin.Context, ctx *AppContext, UserID *mod
clientIP := g.ClientIP() clientIP := g.ClientIP()
msg, err := app.Database.Primary.CreateMessage(ctx, *UserID, channel, sendTimestamp, *Title, Content, priority, UserMessageID, clientIP, SenderName, keytok.KeyTokenID) msg, err := app.Database.Primary.CreateMessage(ctx, user.UserID, channel, sendTimestamp, *Title, Content, priority, UserMessageID, clientIP, SenderName, keytok.KeyTokenID)
if err != nil { if err != nil {
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to create message in db", err)) return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to create message in db", err))
} }
@@ -176,7 +174,7 @@ func (app *Application) SendMessage(g *gin.Context, ctx *AppContext, UserID *mod
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to inc token msg-counter", err)) return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to inc token msg-counter", err))
} }
log.Info().Msg(fmt.Sprintf("Sending new notification %s for user %s (to %d active subscriptions)", msg.MessageID, UserID, len(activeSubscriptions))) log.Info().Msg(fmt.Sprintf("Sending new notification %s for user %s (to %d active subscriptions)", msg.MessageID, user.UserID, len(activeSubscriptions)))
for _, sub := range activeSubscriptions { for _, sub := range activeSubscriptions {
clients, err := app.Database.Primary.ListClients(ctx, sub.SubscriberUserID) clients, err := app.Database.Primary.ListClients(ctx, sub.SubscriberUserID)

View File

@@ -1142,9 +1142,8 @@ func TestChannelMessageCounter(t *testing.T) {
} }
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid, "title": tt.ShortLipsum(1001, 1),
"title": tt.ShortLipsum(1001, 1),
}) })
chan0 := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels", uid)).Channels[0] chan0 := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels", uid)).Channels[0]
@@ -1171,28 +1170,24 @@ func TestChannelMessageCounter(t *testing.T) {
assertCounter(1, 0, 0) assertCounter(1, 0, 0)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid, "title": tt.ShortLipsum(1002, 1),
"title": tt.ShortLipsum(1002, 1),
}) })
assertCounter(2, 0, 0) assertCounter(2, 0, 0)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid,
"channel": "Chan1", "channel": "Chan1",
"title": tt.ShortLipsum(1003, 1), "title": tt.ShortLipsum(1003, 1),
}) })
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid,
"channel": "Chan2", "channel": "Chan2",
"title": tt.ShortLipsum(1004, 1), "title": tt.ShortLipsum(1004, 1),
}) })
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid,
"channel": "Chan2", "channel": "Chan2",
"title": tt.ShortLipsum(1005, 1), "title": tt.ShortLipsum(1005, 1),
}) })

View File

@@ -126,7 +126,6 @@ func TestTokenKeys(t *testing.T) {
msg1s := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ msg1s := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": key7.Token, "key": key7.Token,
"user_id": data.UID,
"channel": "testchan1", "channel": "testchan1",
"title": "HelloWorld_001", "title": "HelloWorld_001",
}) })
@@ -137,15 +136,13 @@ func TestTokenKeys(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{ tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
"key": key7.Token, "key": key7.Token,
"user_id": data.UID,
"channel": "testchan2", "channel": "testchan2",
"title": "HelloWorld_001", "title": "HelloWorld_001",
}, 401, apierr.USER_AUTH_FAILED) // wrong channel }, 401, apierr.USER_AUTH_FAILED) // wrong channel
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{ tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
"key": key7.Token, "key": key7.Token,
"user_id": data.UID, "title": "HelloWorld_001",
"title": "HelloWorld_001",
}, 401, apierr.USER_AUTH_FAILED) // no channel (=main) }, 401, apierr.USER_AUTH_FAILED) // no channel (=main)
tt.RequestAuthGetShouldFail(t, key7.Token, baseUrl, fmt.Sprintf("/api/v2/users/%s", data.UID), 401, apierr.USER_AUTH_FAILED) // no user read perm tt.RequestAuthGetShouldFail(t, key7.Token, baseUrl, fmt.Sprintf("/api/v2/users/%s", data.UID), 401, apierr.USER_AUTH_FAILED) // no user read perm
@@ -160,9 +157,8 @@ func TestTokenKeys(t *testing.T) {
}) })
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{ tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
"key": key8.Token, "key": key8.Token,
"user_id": data.UID, "title": "HelloWorld_001",
"title": "HelloWorld_001",
}, 401, apierr.USER_AUTH_FAILED) // no send perm }, 401, apierr.USER_AUTH_FAILED) // no send perm
} }
@@ -470,15 +466,13 @@ func TestTokenKeysPermissions(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{ tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
"key": key7.Token, "key": key7.Token,
"user_id": data.UID,
"channel": "testchan2", "channel": "testchan2",
"title": "HelloWorld_001", "title": "HelloWorld_001",
}, 401, apierr.USER_AUTH_FAILED) // wrong channel }, 401, apierr.USER_AUTH_FAILED) // wrong channel
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{ tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
"key": key7.Token, "key": key7.Token,
"user_id": data.UID, "title": "HelloWorld_001",
"title": "HelloWorld_001",
}, 401, apierr.USER_AUTH_FAILED) // no channel (=main) }, 401, apierr.USER_AUTH_FAILED) // no channel (=main)
tt.RequestAuthGetShouldFail(t, key7.Token, baseUrl, fmt.Sprintf("/api/v2/users/%s", data.UID), 401, apierr.USER_AUTH_FAILED) // no user read perm tt.RequestAuthGetShouldFail(t, key7.Token, baseUrl, fmt.Sprintf("/api/v2/users/%s", data.UID), 401, apierr.USER_AUTH_FAILED) // no user read perm
@@ -493,9 +487,8 @@ func TestTokenKeysPermissions(t *testing.T) {
}) })
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{ tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
"key": key8.Token, "key": key8.Token,
"user_id": data.UID, "title": "HelloWorld_001",
"title": "HelloWorld_001",
}, 401, apierr.USER_AUTH_FAILED) // no send perm }, 401, apierr.USER_AUTH_FAILED) // no send perm
} }
@@ -550,44 +543,38 @@ func TestTokenKeysMessageCounter(t *testing.T) {
assertCounter(0, 0, 0) assertCounter(0, 0, 0)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid, "title": tt.ShortLipsum(1001, 1),
"title": tt.ShortLipsum(1001, 1),
}) })
assertCounter(1, 0, 0) assertCounter(1, 0, 0)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid, "title": tt.ShortLipsum(1002, 1),
"title": tt.ShortLipsum(1002, 1),
}) })
assertCounter(2, 0, 0) assertCounter(2, 0, 0)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid, "title": tt.ShortLipsum(1002, 1),
"title": tt.ShortLipsum(1002, 1),
}) })
assertCounter(2, 1, 0) assertCounter(2, 1, 0)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid,
"channel": "Chan1", "channel": "Chan1",
"title": tt.ShortLipsum(1003, 1), "title": tt.ShortLipsum(1003, 1),
}) })
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid,
"channel": "Chan2", "channel": "Chan2",
"title": tt.ShortLipsum(1004, 1), "title": tt.ShortLipsum(1004, 1),
}) })
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid,
"channel": "Chan2", "channel": "Chan2",
"title": tt.ShortLipsum(1005, 1), "title": tt.ShortLipsum(1005, 1),
}) })
@@ -597,7 +584,6 @@ func TestTokenKeysMessageCounter(t *testing.T) {
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid,
"channel": "Chan2", "channel": "Chan2",
"title": tt.ShortLipsum(1004, 1), "title": tt.ShortLipsum(1004, 1),
}) })
@@ -605,9 +591,8 @@ func TestTokenKeysMessageCounter(t *testing.T) {
assertCounter(3, 4, 0) assertCounter(3, 4, 0)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid, "title": tt.ShortLipsum(1002, 1),
"title": tt.ShortLipsum(1002, 1),
}) })
assertCounter(4, 4, 0) assertCounter(4, 4, 0)

View File

@@ -2,11 +2,13 @@ package test
import ( import (
"database/sql" "database/sql"
"os"
"testing"
tt "blackforestbytes.com/simplecloudnotifier/test/util"
"git.blackforestbytes.com/BlackForestBytes/goext/exerr" "git.blackforestbytes.com/BlackForestBytes/goext/exerr"
"git.blackforestbytes.com/BlackForestBytes/goext/langext" "git.blackforestbytes.com/BlackForestBytes/goext/langext"
"github.com/glebarez/go-sqlite" "github.com/glebarez/go-sqlite"
"os"
"testing"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@@ -20,3 +22,10 @@ func TestMain(m *testing.M) {
os.Exit(m.Run()) os.Exit(m.Run())
} }
func TestInitFactory(t *testing.T) {
ws, _, stop := tt.StartSimpleWebserver(t)
defer stop()
tt.InitDefaultData(t, ws)
}

View File

@@ -418,14 +418,12 @@ func TestDeleteMessage(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid, "title": "Message_1",
"title": "Message_1",
}) })
tt.RequestAuthGet[tt.Void](t, admintok, baseUrl, "/api/v2/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"])) tt.RequestAuthGet[tt.Void](t, admintok, baseUrl, "/api/v2/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"]))
@@ -446,15 +444,13 @@ func TestDeleteMessageAndResendUsrMsgId(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid, "title": "Message_1",
"title": "Message_1", "msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
"msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
}) })
tt.AssertEqual(t, "suppress_send", false, msg1["suppress_send"]) tt.AssertEqual(t, "suppress_send", false, msg1["suppress_send"])
@@ -462,10 +458,9 @@ func TestDeleteMessageAndResendUsrMsgId(t *testing.T) {
tt.RequestAuthGet[tt.Void](t, admintok, baseUrl, "/api/v2/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"])) tt.RequestAuthGet[tt.Void](t, admintok, baseUrl, "/api/v2/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"]))
msg2 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ msg2 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid, "title": "Message_1",
"title": "Message_1", "msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
"msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
}) })
tt.AssertEqual(t, "suppress_send", true, msg2["suppress_send"]) tt.AssertEqual(t, "suppress_send", true, msg2["suppress_send"])
@@ -475,10 +470,9 @@ func TestDeleteMessageAndResendUsrMsgId(t *testing.T) {
// even though message is deleted, we still get a `suppress_send` on send_message // even though message is deleted, we still get a `suppress_send` on send_message
msg3 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ msg3 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid, "title": "Message_1",
"title": "Message_1", "msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
"msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
}) })
tt.AssertEqual(t, "suppress_send", true, msg3["suppress_send"]) tt.AssertEqual(t, "suppress_send", true, msg3["suppress_send"])
@@ -492,9 +486,8 @@ func TestGetMessageSimple(t *testing.T) {
data := tt.InitDefaultData(t, ws) data := tt.InitDefaultData(t, ws)
msgOut := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ msgOut := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": data.User[0].SendKey, "key": data.User[0].SendKey,
"user_id": data.User[0].UID, "title": "Message_1",
"title": "Message_1",
}) })
msgIn := tt.RequestAuthGet[gin.H](t, data.User[0].AdminKey, baseUrl, "/api/v2/messages/"+fmt.Sprintf("%v", msgOut["scn_msg_id"])) msgIn := tt.RequestAuthGet[gin.H](t, data.User[0].AdminKey, baseUrl, "/api/v2/messages/"+fmt.Sprintf("%v", msgOut["scn_msg_id"]))
@@ -533,7 +526,6 @@ func TestGetMessageFull(t *testing.T) {
msgOut := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ msgOut := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": data.User[0].SendKey, "key": data.User[0].SendKey,
"user_id": data.User[0].UID,
"title": "Message_1", "title": "Message_1",
"content": content, "content": content,
"channel": "demo-channel-007", "channel": "demo-channel-007",
@@ -948,7 +940,6 @@ func TestDeactivatedSubscriptionListMessages(t *testing.T) {
newMessageTitle := langext.RandBase62(48) newMessageTitle := langext.RandBase62(48)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": user15.AdminKey, "key": user15.AdminKey,
"user_id": user15.UID,
"channel": chanName, "channel": chanName,
"title": newMessageTitle, "title": newMessageTitle,
}) })
@@ -1122,7 +1113,6 @@ func TestActiveSubscriptionListMessages(t *testing.T) {
newMessageTitle := langext.RandBase62(48) newMessageTitle := langext.RandBase62(48)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": user15.AdminKey, "key": user15.AdminKey,
"user_id": user15.UID,
"channel": chanName, "channel": chanName,
"title": newMessageTitle, "title": newMessageTitle,
}) })
@@ -1176,7 +1166,6 @@ func TestUnconfirmedSubscriptionListMessages(t *testing.T) {
newMessageTitle := langext.RandBase62(48) newMessageTitle := langext.RandBase62(48)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": user15.AdminKey, "key": user15.AdminKey,
"user_id": user15.UID,
"channel": chanName, "channel": chanName,
"title": newMessageTitle, "title": newMessageTitle,
}) })
@@ -1229,7 +1218,7 @@ func TestListMessagesSubscriptionStatusAllInactiveSubscription(t *testing.T) {
subscriptionID, _ := tt.FindSubscriptionByChanName(t, baseUrl, user14, user15.UID, chanName) subscriptionID, _ := tt.FindSubscriptionByChanName(t, baseUrl, user14, user15.UID, chanName)
newMessageTitle := langext.RandBase62(48) newMessageTitle := langext.RandBase62(48)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{"key": user15.AdminKey, "user_id": user15.UID, "channel": chanName, "title": newMessageTitle}) tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{"key": user15.AdminKey, "channel": chanName, "title": newMessageTitle})
type msg struct { type msg struct {
MessageId string `json:"message_id"` MessageId string `json:"message_id"`
@@ -1282,7 +1271,7 @@ func TestListMessagesSubscriptionStatusAllNoSubscription(t *testing.T) {
chan2 := data.User[0].Channels[2] chan2 := data.User[0].Channels[2]
newMessageTitle := langext.RandBase62(48) newMessageTitle := langext.RandBase62(48)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{"key": user0.AdminKey, "user_id": user0.UID, "channel": chan2.InternalName, "title": newMessageTitle}) tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{"key": user0.AdminKey, "channel": chan2.InternalName, "title": newMessageTitle})
{ {
messages := tt.RequestAuthGet[mglist](t, user0.AdminKey, baseUrl, "/api/v2/messages") messages := tt.RequestAuthGet[mglist](t, user0.AdminKey, baseUrl, "/api/v2/messages")

View File

@@ -1,18 +1,18 @@
package test package test
import ( import (
"blackforestbytes.com/simplecloudnotifier/api/apierr"
"blackforestbytes.com/simplecloudnotifier/models"
"blackforestbytes.com/simplecloudnotifier/push"
tt "blackforestbytes.com/simplecloudnotifier/test/util"
"fmt" "fmt"
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
"github.com/gin-gonic/gin"
"math/rand/v2" "math/rand/v2"
"net/url" "net/url"
"strings" "strings"
"testing" "testing"
"time" "time"
"blackforestbytes.com/simplecloudnotifier/api/apierr"
"blackforestbytes.com/simplecloudnotifier/push"
tt "blackforestbytes.com/simplecloudnotifier/test/util"
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
"github.com/gin-gonic/gin"
) )
func TestSendSimpleMessageJSON(t *testing.T) { func TestSendSimpleMessageJSON(t *testing.T) {
@@ -28,27 +28,23 @@ func TestSendSimpleMessageJSON(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
readtok := r0["read_key"].(string) readtok := r0["read_key"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid, "title": "HelloWorld_001",
"title": "HelloWorld_001",
}) })
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{ tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
"key": readtok, "key": readtok,
"user_id": uid, "title": "HelloWorld_001",
"title": "HelloWorld_001",
}, 401, apierr.USER_AUTH_FAILED) }, 401, apierr.USER_AUTH_FAILED)
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{ tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
"key": "asdf", "key": "asdf",
"user_id": uid, "title": "HelloWorld_001",
"title": "HelloWorld_001",
}, 401, apierr.USER_AUTH_FAILED) }, 401, apierr.USER_AUTH_FAILED)
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
@@ -117,14 +113,12 @@ func TestSendSimpleMessageForm(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", tt.FormData{ msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", tt.FormData{
"key": sendtok, "key": sendtok,
"user_id": uid, "title": "Hello World 9999 [$$$]",
"title": "Hello World 9999 [$$$]",
}) })
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
@@ -189,9 +183,8 @@ func TestSendSimpleMessageJSONAndQuery(t *testing.T) {
// query overwrite body // query overwrite body
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), gin.H{ msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), gin.H{
"key": "ERR", "key": "ERR",
"user_id": models.NewUserID(), "title": "2222222",
"title": "2222222",
}) })
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
@@ -212,21 +205,18 @@ func TestSendSimpleMessageAlt1(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
readtok := r0["read_key"].(string) readtok := r0["read_key"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/send", gin.H{ msg1 := tt.RequestPost[gin.H](t, baseUrl, "/send", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid, "title": "HelloWorld_001",
"title": "HelloWorld_001",
}) })
tt.RequestPostShouldFail(t, baseUrl, "/send", gin.H{ tt.RequestPostShouldFail(t, baseUrl, "/send", gin.H{
"key": readtok, "key": readtok,
"user_id": uid, "title": "HelloWorld_001",
"title": "HelloWorld_001",
}, 401, apierr.USER_AUTH_FAILED) }, 401, apierr.USER_AUTH_FAILED)
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
@@ -259,13 +249,11 @@ func TestSendContentMessage(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid,
"title": "HelloWorld_042", "title": "HelloWorld_042",
"content": "I am Content\nasdf", "content": "I am Content\nasdf",
}) })
@@ -304,13 +292,11 @@ func TestSendWithSendername(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid,
"title": "HelloWorld_xyz", "title": "HelloWorld_xyz",
"content": "Unicode: 日本 - yäy\000\n\t\x00...", "content": "Unicode: 日本 - yäy\000\n\t\x00...",
"sender_name": "localhorst", "sender_name": "localhorst",
@@ -353,7 +339,6 @@ func TestSendLongContent(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
@@ -364,7 +349,6 @@ func TestSendLongContent(t *testing.T) {
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok, "key": sendtok,
"user_id": uid,
"title": "HelloWorld_042", "title": "HelloWorld_042",
"content": longContent, "content": longContent,
}) })

View File

@@ -401,36 +401,31 @@ func TestUserMessageCounter(t *testing.T) {
assertCounter(0) assertCounter(0)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid, "title": tt.ShortLipsum(1001, 1),
"title": tt.ShortLipsum(1001, 1),
}) })
assertCounter(1) assertCounter(1)
assertCounter(1) assertCounter(1)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid, "title": tt.ShortLipsum(1002, 1),
"title": tt.ShortLipsum(1002, 1),
}) })
assertCounter(2) assertCounter(2)
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid, "title": tt.ShortLipsum(1003, 1),
"title": tt.ShortLipsum(1003, 1),
}) })
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid, "title": tt.ShortLipsum(1004, 1),
"title": tt.ShortLipsum(1004, 1),
}) })
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": admintok, "key": admintok,
"user_id": uid, "title": tt.ShortLipsum(1005, 1),
"title": tt.ShortLipsum(1005, 1),
}) })
assertCounter(5) assertCounter(5)

View File

@@ -1,15 +1,16 @@
package util package util
import ( import (
"blackforestbytes.com/simplecloudnotifier/logic"
"fmt" "fmt"
"testing"
"time"
"blackforestbytes.com/simplecloudnotifier/logic"
"git.blackforestbytes.com/BlackForestBytes/goext/langext" "git.blackforestbytes.com/BlackForestBytes/goext/langext"
"git.blackforestbytes.com/BlackForestBytes/goext/timeext" "git.blackforestbytes.com/BlackForestBytes/goext/timeext"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"gopkg.in/loremipsum.v1" "gopkg.in/loremipsum.v1"
"testing"
"time"
) )
// # Generated by https://chat.openai.com/chat // # Generated by https://chat.openai.com/chat
@@ -393,7 +394,6 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
for _, mex := range messageExamples { for _, mex := range messageExamples {
body := gin.H{} body := gin.H{}
body["title"] = mex.Title body["title"] = mex.Title
body["user_id"] = users[mex.User].UID
switch mex.Key { switch mex.Key {
case AKEY: case AKEY:
body["key"] = users[mex.User].AdminKey body["key"] = users[mex.User].AdminKey

View File

@@ -19,10 +19,9 @@
<a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a> <a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
<p>Get your user-id and user-key from the android or iOS app.<br/>And send notifications to your phone by performing a POST request against <code>{{config|baseURL}}/</code> from anywhere</p> <p>Get your user-key from the android or iOS app.<br/>And send notifications to your phone by performing a POST request against <code>{{config|baseURL}}/</code> from anywhere</p>
<pre> <pre>
curl \ curl \
--data "user_id=${userid}" \
--data "key=${key}" \ --data "key=${key}" \
--data "title=${message_title}" \ --data "title=${message_title}" \
--data "content=${message_body}" \ --data "content=${message_body}" \
@@ -35,7 +34,6 @@ curl \
<p>Most parameters are optional, you can send a message with only a title (default priority and channel will be used)</p> <p>Most parameters are optional, you can send a message with only a title (default priority and channel will be used)</p>
<pre> <pre>
curl \ curl \
--data "user_id={userid}" \
--data "key={key}" \ --data "key={key}" \
--data "title={message_title}" \ --data "title={message_title}" \
{{config|baseURL}}/</pre> {{config|baseURL}}/</pre>

View File

@@ -52,7 +52,7 @@
All Parameters can either directly be submitted as URL parameters or they can be put into the POST body (either multipart/form-data or JSON). All Parameters can either directly be submitted as URL parameters or they can be put into the POST body (either multipart/form-data or JSON).
</p> </p>
<p> <p>
You <i>need</i> to supply a valid <code>[user_id, key]</code> pair and a <code>title</code> for your message, all other parameter are optional. You <i>need</i> to supply a valid <code>key</code> and a <code>title</code> for your message, all other parameter are optional.
</p> </p>
</div> </div>
@@ -90,7 +90,7 @@
</tr> </tr>
<tr> <tr>
<td data-label="Statuscode">401 (Unauthorized)</td> <td data-label="Statuscode">401 (Unauthorized)</td>
<td data-label="Explanation">The user_id was not found, the key is wrong or the [user_id, key] combination does not have the SEND permissions on the specified channel</td> <td data-label="Explanation">The key is wrong or does not have the SEND permissions on the specified channel</td>
</tr> </tr>
<tr> <tr>
<td data-label="Statuscode">403 (Forbidden)</td> <td data-label="Statuscode">403 (Forbidden)</td>
@@ -125,7 +125,6 @@
If needed the content can be supplied in the <code>content</code> parameter. If needed the content can be supplied in the <code>content</code> parameter.
</p> </p>
<pre>curl \ <pre>curl \
--data "user_id={userid}" \
--data "key={key}" \ --data "key={key}" \
--data "title={message_title}" \ --data "title={message_title}" \
--data "content={message_content}" \ --data "content={message_content}" \
@@ -143,7 +142,6 @@
If no priority is supplied the message will get the default priority of 1. If no priority is supplied the message will get the default priority of 1.
</p> </p>
<pre>curl \ <pre>curl \
--data "user_id={userid}" \
--data "key={key}" \ --data "key={key}" \
--data "title={message_title}" \ --data "title={message_title}" \
--data "priority={0|1|2}" \ --data "priority={0|1|2}" \
@@ -158,7 +156,6 @@
Channel names are case-insensitive and can only contain letters, numbers, underscores and minuses ( <code>/[[:alnum:]\-_]+/</code> ) Channel names are case-insensitive and can only contain letters, numbers, underscores and minuses ( <code>/[[:alnum:]\-_]+/</code> )
</p> </p>
<pre>curl \ <pre>curl \
--data "user_id={userid}" \
--data "key={key}" \ --data "key={key}" \
--data "title={message_title}" \ --data "title={message_title}" \
--data "channel={my_channel}" \ --data "channel={my_channel}" \
@@ -229,7 +226,6 @@
The message_id is optional - but if you want to use it you need to supply it via the <code>msg_id</code> parameter. The message_id is optional - but if you want to use it you need to supply it via the <code>msg_id</code> parameter.
</p> </p>
<pre>curl \ <pre>curl \
--data "user_id={userid}" \
--data "key={key}" \ --data "key={key}" \
--data "title={message_title}" \ --data "title={message_title}" \
--data "msg_id={message_id}" \ --data "msg_id={message_id}" \
@@ -248,7 +244,6 @@
The custom timestamp must be within 48 hours of the current time. This parameter is only intended to supply a more precise value in case the message sending was delayed. The custom timestamp must be within 48 hours of the current time. This parameter is only intended to supply a more precise value in case the message sending was delayed.
</p> </p>
<pre>curl \ <pre>curl \
--data "user_id={userid}" \
--data "key={key}" \ --data "key={key}" \
--data "title={message_title}" \ --data "title={message_title}" \
--data "timestamp={unix_timestamp}" \ --data "timestamp={unix_timestamp}" \

View File

@@ -21,11 +21,6 @@
<a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a> <a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
<div class="row responsive-label">
<div class="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div>
<div class="col-sm-12 col-md"><input placeholder="UserID" id="uid" class="doc" type="text" pattern="USR[A-Za-z0-9]{21}"></div>
</div>
<div class="row responsive-label"> <div class="row responsive-label">
<div class="col-sm-12 col-md-3"><label for="ukey" class="doc">Authentification Key</label></div> <div class="col-sm-12 col-md-3"><label for="ukey" class="doc">Authentification Key</label></div>
<div class="col-sm-12 col-md"><input placeholder="Key" id="ukey" class="doc" type="text" pattern="[A-Za-z0-9]{64}"></div> <div class="col-sm-12 col-md"><input placeholder="Key" id="ukey" class="doc" type="text" pattern="[A-Za-z0-9]{64}"></div>

View File

@@ -8,20 +8,17 @@ function send()
me.classList.add("btn-disabled"); me.classList.add("btn-disabled");
let uid = document.getElementById("uid");
let key = document.getElementById("ukey"); let key = document.getElementById("ukey");
let tit = document.getElementById("tit"); let tit = document.getElementById("tit");
let cnt = document.getElementById("cnt"); let cnt = document.getElementById("cnt");
let pio = document.getElementById("prio"); let pio = document.getElementById("prio");
let cha = document.getElementById("chan"); let cha = document.getElementById("chan");
uid.classList.remove('input-invalid');
key.classList.remove('input-invalid'); key.classList.remove('input-invalid');
cnt.classList.remove('input-invalid'); cnt.classList.remove('input-invalid');
pio.classList.remove('input-invalid'); pio.classList.remove('input-invalid');
let data = new FormData(); let data = new FormData();
data.append('user_id', uid.value);
data.append('key', key.value); data.append('key', key.value);
if (tit.value !== '') data.append('title', tit.value); if (tit.value !== '') data.append('title', tit.value);
if (cnt.value !== '') data.append('content', cnt.value); if (cnt.value !== '') data.append('content', cnt.value);
@@ -40,7 +37,6 @@ function send()
let resp = JSON.parse(xhr.responseText); let resp = JSON.parse(xhr.responseText);
if (!resp.success || xhr.status !== 200) if (!resp.success || xhr.status !== 200)
{ {
if (resp.errhighlight === 101) uid.classList.add('input-invalid');
if (resp.errhighlight === 102) key.classList.add('input-invalid'); if (resp.errhighlight === 102) key.classList.add('input-invalid');
if (resp.errhighlight === 103) tit.classList.add('input-invalid'); if (resp.errhighlight === 103) tit.classList.add('input-invalid');
if (resp.errhighlight === 104) cnt.classList.add('input-invalid'); if (resp.errhighlight === 104) cnt.classList.add('input-invalid');
@@ -63,7 +59,6 @@ function send()
'&quota=' + resp.quota + '&quota=' + resp.quota +
'&quota_remain=' + (resp.quota_max-resp.quota) + '&quota_remain=' + (resp.quota_max-resp.quota) +
'&quota_max=' + resp.quota_max + '&quota_max=' + resp.quota_max +
'&preset_user_id=' + uid.value +
'&preset_user_key=' + key.value + '&preset_user_key=' + key.value +
'&preset_channel=' + cha.value; '&preset_channel=' + cha.value;
} }
@@ -89,7 +84,6 @@ window.addEventListener("load", function ()
const qp = new URLSearchParams(window.location.search); const qp = new URLSearchParams(window.location.search);
let btn = document.getElementById("btnSend"); let btn = document.getElementById("btnSend");
let uid = document.getElementById("uid");
let key = document.getElementById("ukey"); let key = document.getElementById("ukey");
let tit = document.getElementById("tit"); let tit = document.getElementById("tit");
let cnt = document.getElementById("cnt"); let cnt = document.getElementById("cnt");
@@ -100,7 +94,6 @@ window.addEventListener("load", function ()
if (qp.has('preset_priority')) pio.selectedIndex = parseInt(qp.get("preset_priority")); if (qp.has('preset_priority')) pio.selectedIndex = parseInt(qp.get("preset_priority"));
if (qp.has('preset_user_key')) key.value = qp.get("preset_user_key"); if (qp.has('preset_user_key')) key.value = qp.get("preset_user_key");
if (qp.has('preset_user_id')) uid.value = qp.get("preset_user_id");
if (qp.has('preset_title')) tit.value = qp.get("preset_title"); if (qp.has('preset_title')) tit.value = qp.get("preset_title");
if (qp.has('preset_content')) cnt.value = qp.get("preset_content"); if (qp.has('preset_content')) cnt.value = qp.get("preset_content");
if (qp.has('preset_channel')) cha.value = qp.get("preset_channel"); if (qp.has('preset_channel')) cha.value = qp.get("preset_channel");