added UpdateClient route

This commit is contained in:
Mike Schwörer 2023-05-28 23:25:18 +02:00
parent 3a9b15c2be
commit 0daca2cf8f
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
19 changed files with 271 additions and 44 deletions

View File

@ -14,7 +14,6 @@
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/_pygments" />
<excludeFolder url="file://$MODULE_DIR$/swagger" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

View File

@ -27,7 +27,7 @@ import (
// @ID api-channels-list
// @Tags API-v2
//
// @Param uid path int true "UserID"
// @Param uid path string true "UserID"
// @Param selector query string false "Filter channels (default: owned)" Enums(owned, subscribed, all, subscribed_any, all_any)
//
// @Success 200 {object} handler.ListChannels.response
@ -162,7 +162,7 @@ func (h APIHandler) GetChannel(g *gin.Context) ginresp.HTTPResponse {
// @ID api-channels-create
// @Tags API-v2
//
// @Param uid path int true "UserID"
// @Param uid path string true "UserID"
// @Param post_body body handler.CreateChannel.body false " "
//
// @Success 200 {object} models.ChannelWithSubscriptionJSON
@ -260,8 +260,8 @@ func (h APIHandler) CreateChannel(g *gin.Context) ginresp.HTTPResponse {
// @ID api-channels-update
// @Tags API-v2
//
// @Param uid path int true "UserID"
// @Param cid path int true "ChannelID"
// @Param uid path string true "UserID"
// @Param cid path string true "ChannelID"
//
// @Param subscribe_key body string false "Send `true` to create a new subscribe_key"
// @Param send_key body string false "Send `true` to create a new send_key"
@ -378,8 +378,8 @@ func (h APIHandler) UpdateChannel(g *gin.Context) ginresp.HTTPResponse {
// @Tags API-v2
//
// @Param query_data query handler.ListChannelMessages.query false " "
// @Param uid path int true "UserID"
// @Param cid path int true "ChannelID"
// @Param uid path string true "UserID"
// @Param cid path string true "ChannelID"
//
// @Success 200 {object} handler.ListChannelMessages.response
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"

View File

@ -103,7 +103,7 @@ func (h APIHandler) GetClient(g *gin.Context) ginresp.HTTPResponse {
// @ID api-clients-create
// @Tags API-v2
//
// @Param uid path int true "UserID"
// @Param uid path string true "UserID"
//
// @Param post_body body handler.AddClient.body false " "
//
@ -206,3 +206,89 @@ func (h APIHandler) DeleteClient(g *gin.Context) ginresp.HTTPResponse {
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, client.JSON()))
}
// UpdateClient swaggerdoc
//
// @Summary (Partially) update a client
// @Description The body-values are optional, only send the ones you want to update
// @ID api-client-update
// @Tags API-v2
//
// @Param uid path string true "UserID"
// @Param cid path string true "ClientID"
//
// @Param clientname body string false "Change the clientname (send an empty string to clear it)"
// @Param pro_token body string false "Send a verification of premium purchase"
//
// @Success 200 {object} models.ClientJSON
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "client is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "client not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
//
// @Router /api/v2/users/{uid}/clients/{cid} [PATCH]
func (h APIHandler) UpdateClient(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid" binding:"entityid"`
ClientID models.ClientID `uri:"cid" binding:"entityid"`
}
type body struct {
FCMToken *string `json:"fcm_token"`
AgentModel *string `json:"agent_model"`
AgentVersion *string `json:"agent_version"`
}
var u uri
var b body
ctx, errResp := h.app.StartRequest(g, &u, nil, &b, nil)
if errResp != nil {
return *errResp
}
defer ctx.Cancel()
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp
}
client, err := h.database.GetClient(ctx, u.UserID, u.ClientID)
if err == sql.ErrNoRows {
return ginresp.APIError(g, 404, apierr.CLIENT_NOT_FOUND, "Client not found", err)
}
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query client", err)
}
if b.FCMToken != nil && *b.FCMToken != client.FCMToken {
err = h.database.DeleteClientsByFCM(ctx, *b.FCMToken)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to delete existing clients in db", err)
}
err = h.database.UpdateClientFCMToken(ctx, u.ClientID, *b.FCMToken)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update client", err)
}
}
if b.AgentModel != nil {
err = h.database.UpdateClientAgentModel(ctx, u.ClientID, *b.AgentModel)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update client", err)
}
}
if b.AgentVersion != nil {
err = h.database.UpdateClientAgentVersion(ctx, u.ClientID, *b.AgentVersion)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update client", err)
}
}
client, err = h.database.GetClient(ctx, u.UserID, u.ClientID)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query (updated) client", err)
}
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, client.JSON()))
}

View File

@ -106,8 +106,8 @@ func (h APIHandler) GetUserKey(g *gin.Context) ginresp.HTTPResponse {
// @ID api-tokenkeys-update
// @Tags API-v2
//
// @Param uid path int true "UserID"
// @Param kid path int true "TokenKeyID"
// @Param uid path string true "UserID"
// @Param kid path string true "TokenKeyID"
//
// @Param post_body body handler.UpdateUserKey.body false " "
//
@ -204,7 +204,7 @@ func (h APIHandler) UpdateUserKey(g *gin.Context) ginresp.HTTPResponse {
// @ID api-tokenkeys-create
// @Tags API-v2
//
// @Param uid path int true "UserID"
// @Param uid path string true "UserID"
//
// @Param post_body body handler.CreateUserKey.body false " "
//

View File

@ -25,7 +25,7 @@ import (
// @ID api-user-subscriptions-list
// @Tags API-v2
//
// @Param uid path int true "UserID"
// @Param uid path string true "UserID"
// @Param selector query string true "Filter subscriptions (default: outgoing_all)" Enums(outgoing_all, outgoing_confirmed, outgoing_unconfirmed, incoming_all, incoming_confirmed, incoming_unconfirmed)
//
// @Success 200 {object} handler.ListUserSubscriptions.response
@ -275,7 +275,7 @@ func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse {
// @ID api-subscriptions-create
// @Tags API-v2
//
// @Param uid path int true "UserID"
// @Param uid path string true "UserID"
// @Param query_data query handler.CreateSubscription.query false " "
// @Param post_data body handler.CreateSubscription.body false " "
//
@ -363,8 +363,8 @@ func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse {
// @ID api-subscriptions-update
// @Tags API-v2
//
// @Param uid path int true "UserID"
// @Param sid path int true "SubscriptionID"
// @Param uid path string true "UserID"
// @Param sid path string true "SubscriptionID"
// @Param post_data body handler.UpdateSubscription.body false " "
//
// @Success 200 {object} models.SubscriptionJSON

View File

@ -180,7 +180,7 @@ func (h APIHandler) GetUser(g *gin.Context) ginresp.HTTPResponse {
// @ID api-user-update
// @Tags API-v2
//
// @Param uid path int true "UserID"
// @Param uid path string true "UserID"
//
// @Param username body string false "Change the username (send an empty string to clear it)"
// @Param pro_token body string false "Send a verification of premium purchase"

View File

@ -325,8 +325,6 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(0, "Failed to query clients")
}
fcmSet := langext.ArrAny(clients, func(c models.Client) bool { return c.FCMToken != nil })
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
Success: true,
Message: "ok",
@ -335,7 +333,7 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse {
QuotaUsed: user.QuotaUsedToday(),
QuotaMax: user.QuotaPerDay(),
IsPro: langext.Conditional(user.IsPro, 1, 0),
FCMSet: fcmSet,
FCMSet: len(clients) > 0,
UnackCount: 0,
}))
}

View File

@ -307,7 +307,7 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to create delivery", err))
}
} else {
_, err = h.database.CreateSuccessDelivery(ctx, client, msg, *fcmDelivID)
_, err = h.database.CreateSuccessDelivery(ctx, client, msg, fcmDelivID)
if err != nil {
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to create delivery", err))
}

View File

@ -135,6 +135,7 @@ func (r *Router) Init(e *gin.Engine) error {
apiv2.GET("/users/:uid/clients", r.Wrap(r.apiHandler.ListClients))
apiv2.GET("/users/:uid/clients/:cid", r.Wrap(r.apiHandler.GetClient))
apiv2.PATCH("/users/:uid/clients/:cid", r.Wrap(r.apiHandler.UpdateClient))
apiv2.POST("/users/:uid/clients", r.Wrap(r.apiHandler.AddClient))
apiv2.DELETE("/users/:uid/clients/:cid", r.Wrap(r.apiHandler.DeleteClient))

View File

@ -2,7 +2,6 @@ package primary
import (
"blackforestbytes.com/simplecloudnotifier/models"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/sq"
"time"
)
@ -17,7 +16,7 @@ func (db *Database) CreateClient(ctx TxContext, userid models.UserID, ctype mode
ClientID: models.NewClientID(),
UserID: userid,
Type: ctype,
FCMToken: langext.Ptr(fcmToken),
FCMToken: fcmToken,
TimestampCreated: time2DB(time.Now()),
AgentModel: agentModel,
AgentVersion: agentVersion,
@ -113,3 +112,54 @@ func (db *Database) DeleteClientsByFCM(ctx TxContext, fcmtoken string) error {
return nil
}
func (db *Database) UpdateClientFCMToken(ctx TxContext, clientid models.ClientID, fcmtoken string) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE clients SET fcm_token = :vvv WHERE client_id = :cid", sq.PP{
"vvv": fcmtoken,
"cid": clientid,
})
if err != nil {
return err
}
return nil
}
func (db *Database) UpdateClientAgentModel(ctx TxContext, clientid models.ClientID, agentModel string) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE clients SET agent_model = :vvv WHERE client_id = :cid", sq.PP{
"vvv": agentModel,
"cid": clientid,
})
if err != nil {
return err
}
return nil
}
func (db *Database) UpdateClientAgentVersion(ctx TxContext, clientid models.ClientID, agentVersion string) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE clients SET agent_version = :vvv WHERE client_id = :cid", sq.PP{
"vvv": agentVersion,
"cid": clientid,
})
if err != nil {
return err
}
return nil
}

View File

@ -50,7 +50,7 @@ CREATE TABLE clients
user_id TEXT NOT NULL,
type TEXT CHECK(type IN ('ANDROID', 'IOS')) NOT NULL,
fcm_token TEXT NULL,
fcm_token TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,

View File

@ -9,7 +9,7 @@ require (
github.com/jmoiron/sqlx v1.3.5
github.com/mattn/go-sqlite3 v1.14.16
github.com/rs/zerolog v1.28.0
gogs.mikescher.com/BlackForestBytes/goext v0.0.126
gogs.mikescher.com/BlackForestBytes/goext v0.0.127
gopkg.in/loremipsum.v1 v1.1.0
)

View File

@ -81,6 +81,8 @@ gogs.mikescher.com/BlackForestBytes/goext v0.0.125 h1:l4C5/CQS/IVm364oZa8MqiG62+
gogs.mikescher.com/BlackForestBytes/goext v0.0.125/go.mod h1:w8JlyUHpoOJmW5GxsiheZkFh3vn8Mp80ynSVOFLszL0=
gogs.mikescher.com/BlackForestBytes/goext v0.0.126 h1:o3u0STRaPkmNBenqzvXAOPALSyRswuDwl3Y/0MVHAx4=
gogs.mikescher.com/BlackForestBytes/goext v0.0.126/go.mod h1:w8JlyUHpoOJmW5GxsiheZkFh3vn8Mp80ynSVOFLszL0=
gogs.mikescher.com/BlackForestBytes/goext v0.0.127 h1:NYrSO1kjFWL1yMh0xqIeMZ4hUJiGjILMQwvZMLy4VEg=
gogs.mikescher.com/BlackForestBytes/goext v0.0.127/go.mod h1:w8JlyUHpoOJmW5GxsiheZkFh3vn8Mp80ynSVOFLszL0=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=

View File

@ -158,7 +158,7 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
fcmDelivID, err := j.app.DeliverMessage(ctx, client, msg, nil)
if err == nil {
err = j.app.Database.Primary.SetDeliverySuccess(ctx, delivery, *fcmDelivID)
err = j.app.Database.Primary.SetDeliverySuccess(ctx, delivery, fcmDelivID)
if err != nil {
log.Err(err).Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Failed to update delivery")
ctx.RollbackTransaction()

View File

@ -339,17 +339,13 @@ func (app *Application) NormalizeUsername(v string) string {
return strings.TrimSpace(v)
}
func (app *Application) DeliverMessage(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string) (*string, error) {
if client.FCMToken != nil {
func (app *Application) DeliverMessage(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string) (string, error) {
fcmDelivID, err := app.Pusher.SendNotification(ctx, client, msg, compatTitleOverride)
if err != nil {
log.Warn().Str("MessageID", msg.MessageID.String()).Str("ClientID", client.ClientID.String()).Err(err).Msg("FCM Delivery failed")
return nil, err
}
return langext.Ptr(fcmDelivID), nil
} else {
return langext.Ptr(""), nil
return "", err
}
return fcmDelivID, nil
}
func (app *Application) InsertRequestLog(data models.RequestLog) {

View File

@ -18,7 +18,7 @@ type Client struct {
ClientID ClientID
UserID UserID
Type ClientType
FCMToken *string
FCMToken string
TimestampCreated time.Time
AgentModel string
AgentVersion string
@ -40,7 +40,7 @@ type ClientJSON struct {
ClientID ClientID `json:"client_id"`
UserID UserID `json:"user_id"`
Type ClientType `json:"type"`
FCMToken *string `json:"fcm_token"`
FCMToken string `json:"fcm_token"`
TimestampCreated string `json:"timestamp_created"`
AgentModel string `json:"agent_model"`
AgentVersion string `json:"agent_version"`
@ -50,7 +50,7 @@ type ClientDB struct {
ClientID ClientID `db:"client_id"`
UserID UserID `db:"user_id"`
Type ClientType `db:"type"`
FCMToken *string `db:"fcm_token"`
FCMToken string `db:"fcm_token"`
TimestampCreated int64 `db:"timestamp_created"`
AgentModel string `db:"agent_model"`
AgentVersion string `db:"agent_version"`

View File

@ -65,7 +65,7 @@ func (fb FirebaseConnector) SendNotification(ctx context.Context, client models.
"title": langext.Coalesce(compatTitleOverride, msg.Title),
"body": langext.Coalesce(msg.TrimmedContent(), ""),
},
"token": *client.FCMToken,
"token": client.FCMToken,
"android": gin.H{
"priority": "high",
},

View File

@ -57,7 +57,42 @@ func TestGetClient(t *testing.T) {
tt.AssertJsonMapEqual(t, "client", r3, c0)
}
func TestCreateAndDeleteClient(t *testing.T) {
func TestCreateClient(t *testing.T) {
_, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/v2/users", gin.H{
"agent_model": "DUMMY_PHONE",
"agent_version": "4X",
"client_type": "ANDROID",
"fcm_token": "DUMMY_FCM",
})
uid := fmt.Sprintf("%v", r0["user_id"])
tt.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)
tt.RequestAuthPost[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/clients", gin.H{
"agent_model": "DUMMY_PHONE_2",
"agent_version": "99X",
"client_type": "IOS",
"fcm_token": "DUMMY_FCM_2",
})
type rt3 struct {
Clients []gin.H `json:"clients"`
}
r3 := tt.RequestAuthGet[rt3](t, admintok, baseUrl, "/api/v2/users/"+uid+"/clients")
tt.AssertEqual(t, "len(clients)", 2, len(r3.Clients))
}
func TestDeleteClient(t *testing.T) {
_, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
@ -191,3 +226,63 @@ func TestListClients(t *testing.T) {
tt.RequestAuthGetShouldFail(t, data.User[0].AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/clients", url.QueryEscape(data.User[1].UID)), 401, apierr.USER_AUTH_FAILED)
}
func TestUpdateClient(t *testing.T) {
_, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/v2/users", gin.H{
"agent_model": "DUMMY_PHONE",
"agent_version": "4X",
"client_type": "ANDROID",
"fcm_token": "DUMMY_FCM",
})
uid := fmt.Sprintf("%v", r0["user_id"])
tt.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)
r2 := tt.RequestAuthPost[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/clients", gin.H{
"agent_model": "DUMMY_PHONE_2",
"agent_version": "99X",
"client_type": "IOS",
"fcm_token": "DUMMY_FCM_2",
})
cid2 := fmt.Sprintf("%v", r2["client_id"])
type rt3 struct {
Clients []gin.H `json:"clients"`
}
r3 := tt.RequestAuthGet[rt3](t, admintok, baseUrl, "/api/v2/users/"+uid+"/clients")
tt.AssertEqual(t, "len(clients)", 2, len(r3.Clients))
r4 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/clients/"+cid2)
tt.AssertEqual(t, "agent_model", "DUMMY_PHONE_2", r4["agent_model"])
tt.AssertEqual(t, "agent_version", "99X", r4["agent_version"])
tt.AssertEqual(t, "client_type", "IOS", r4["type"])
tt.AssertEqual(t, "fcm_token", "DUMMY_FCM_2", r4["fcm_token"])
r5 := tt.RequestAuthPatch[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/clients/"+cid2, gin.H{
"agent_model": "PP_DUMMY_PHONE_2",
"agent_version": "PP_99X",
"fcm_token": "PP_DUMMY_FCM_2",
})
tt.AssertEqual(t, "agent_model", "PP_DUMMY_PHONE_2", r5["agent_model"])
tt.AssertEqual(t, "agent_version", "PP_99X", r5["agent_version"])
tt.AssertEqual(t, "client_type", "IOS", r5["type"])
tt.AssertEqual(t, "fcm_token", "PP_DUMMY_FCM_2", r5["fcm_token"])
r6 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/clients/"+cid2)
tt.AssertEqual(t, "agent_model", "PP_DUMMY_PHONE_2", r6["agent_model"])
tt.AssertEqual(t, "agent_version", "PP_99X", r6["agent_version"])
tt.AssertEqual(t, "client_type", "IOS", r6["type"])
tt.AssertEqual(t, "fcm_token", "PP_DUMMY_FCM_2", r6["fcm_token"])
}

View File

@ -477,7 +477,7 @@ func TestCompatUpdateUserKey(t *testing.T) {
s0 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/send.php?user_id=%d&user_key=%s&title=%s", userid, userkey, url.QueryEscape("msg_1")), nil)
tt.AssertEqual(t, "success", true, s0["success"])
tt.AssertEqual(t, "fcm", "DUMMY_FCM", *pusher.Last().Client.FCMToken)
tt.AssertEqual(t, "fcm", "DUMMY_FCM", pusher.Last().Client.FCMToken)
upd := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/update.php?user_id=%d&user_key=%s", userid, userkey))
tt.AssertEqual(t, "success", true, upd["success"])
@ -497,7 +497,7 @@ func TestCompatUpdateUserKey(t *testing.T) {
s1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/send.php?user_id=%d&user_key=%s&title=%s", userid, newkey, url.QueryEscape("msg_2")), nil)
tt.AssertEqual(t, "success", true, s1["success"])
tt.AssertEqual(t, "fcm", "DUMMY_FCM", *pusher.Last().Client.FCMToken)
tt.AssertEqual(t, "fcm", "DUMMY_FCM", pusher.Last().Client.FCMToken)
}
func TestCompatUpdateFCM(t *testing.T) {
@ -514,7 +514,7 @@ func TestCompatUpdateFCM(t *testing.T) {
s0 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/send.php?user_id=%d&user_key=%s&title=%s", userid, userkey, url.QueryEscape("msg_1")), nil)
tt.AssertEqual(t, "success", true, s0["success"])
tt.AssertEqual(t, "fcm", "DUMMY_FCM", *pusher.Last().Client.FCMToken)
tt.AssertEqual(t, "fcm", "DUMMY_FCM", pusher.Last().Client.FCMToken)
upd := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/update.php?user_id=%d&user_key=%s&fcm_token=%s", userid, userkey, "NEW_FCM"))
tt.AssertEqual(t, "success", true, upd["success"])
@ -534,7 +534,7 @@ func TestCompatUpdateFCM(t *testing.T) {
s1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/send.php?user_id=%d&user_key=%s&title=%s", userid, newkey, url.QueryEscape("msg_2")), nil)
tt.AssertEqual(t, "success", true, s1["success"])
tt.AssertEqual(t, "fcm", "NEW_FCM", *pusher.Last().Client.FCMToken)
tt.AssertEqual(t, "fcm", "NEW_FCM", pusher.Last().Client.FCMToken)
}
func TestCompatUpgrade(t *testing.T) {