Tests[ListChannelsDefault, ListChannelsOwned, ListChannelsSubscribedAny, ListChannelsAllAny, ListChannelsSubscribed, ListChannelsAll]

This commit is contained in:
Mike Schwörer 2022-12-22 17:29:59 +01:00
parent 984470b47d
commit 56d9f977ae
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
9 changed files with 115 additions and 51 deletions

View File

@ -19,12 +19,16 @@
- Pagination for ListChannels / ListSubscriptions / ListClients / ListChannelSubscriptions / ListUserSubscriptions - Pagination for ListChannels / ListSubscriptions / ListClients / ListChannelSubscriptions / ListUserSubscriptions
- cannot open sqlite in dbbrowsr (cannot parse schema?)
------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------
- in my script: use (backupname || hostname) for sendername - in my script: use (backupname || hostname) for sendername
------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------
- (?) use uuid ids (also prevents wrong-joins?)
- (?) default-priority for channels - (?) default-priority for channels
- (?) ack/read deliveries && return ack-count (? or not, how to query?) - (?) ack/read deliveries && return ack-count (? or not, how to query?)

View File

@ -1268,14 +1268,15 @@ func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse {
// @ID api-subscriptions-update // @ID api-subscriptions-update
// @Tags API-v2 // @Tags API-v2
// //
// @Param uid path int true "UserID" // @Param uid path int true "UserID"
// @Param sid path int true "SubscriptionID" // @Param sid path int true "SubscriptionID"
// @Param post_data body handler.UpdateSubscription.body false " "
// //
// @Success 200 {object} models.SubscriptionJSON // @Success 200 {object} models.SubscriptionJSON
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid" // @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions" // @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "subscription not found" // @Failure 404 {object} ginresp.apiError "subscription not found"
// @Failure 500 {object} ginresp.apiError "internal server error" // @Failure 500 {object} ginresp.apiError "internal server error"
// //
// @Router /api/users/{uid}/subscriptions/{sid} [PATCH] // @Router /api/users/{uid}/subscriptions/{sid} [PATCH]
func (h APIHandler) UpdateSubscription(g *gin.Context) ginresp.HTTPResponse { func (h APIHandler) UpdateSubscription(g *gin.Context) ginresp.HTTPResponse {
@ -1299,6 +1300,8 @@ func (h APIHandler) UpdateSubscription(g *gin.Context) ginresp.HTTPResponse {
return *permResp return *permResp
} }
userid := *ctx.GetPermissionUserID()
subscription, err := h.database.GetSubscription(ctx, u.SubscriptionID) subscription, err := h.database.GetSubscription(ctx, u.SubscriptionID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return ginresp.APIError(g, 404, apierr.SUBSCRIPTION_NOT_FOUND, "Subscription not found", err) return ginresp.APIError(g, 404, apierr.SUBSCRIPTION_NOT_FOUND, "Subscription not found", err)
@ -1306,11 +1309,14 @@ func (h APIHandler) UpdateSubscription(g *gin.Context) ginresp.HTTPResponse {
if err != nil { if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err) return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
} }
if subscription.SubscriberUserID != u.UserID { if subscription.SubscriberUserID != u.UserID && subscription.ChannelOwnerUserID != u.UserID {
return ginresp.APIError(g, 404, apierr.SUBSCRIPTION_USER_MISMATCH, "Subscription not found", nil) return ginresp.APIError(g, 404, apierr.SUBSCRIPTION_USER_MISMATCH, "Subscription not found", nil)
} }
if b.Confirmed != nil { if b.Confirmed != nil {
if subscription.ChannelOwnerUserID != userid {
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
}
err = h.database.UpdateSubscriptionConfirmed(ctx, u.SubscriptionID, *b.Confirmed) err = h.database.UpdateSubscriptionConfirmed(ctx, u.SubscriptionID, *b.Confirmed)
if err != nil { if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update subscription", err) return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update subscription", err)

View File

@ -69,7 +69,7 @@ func (h CommonHandler) Ping(g *gin.Context) ginresp.HTTPResponse {
// DatabaseTest swaggerdoc // DatabaseTest swaggerdoc
// //
// @Summary Check for a wroking database connection // @Summary Check for a working database connection
// @ID api-common-dbtest // @ID api-common-dbtest
// @Tags Common // @Tags Common
// //

View File

@ -127,10 +127,10 @@ func (r *Router) Init(e *gin.Engine) {
apiv2.GET("/users/:uid/channels/:cid/subscriptions", ginresp.Wrap(r.apiHandler.ListChannelSubscriptions)) apiv2.GET("/users/:uid/channels/:cid/subscriptions", ginresp.Wrap(r.apiHandler.ListChannelSubscriptions))
apiv2.GET("/users/:uid/subscriptions", ginresp.Wrap(r.apiHandler.ListUserSubscriptions)) apiv2.GET("/users/:uid/subscriptions", ginresp.Wrap(r.apiHandler.ListUserSubscriptions))
apiv2.POST("/users/:uid/subscriptions", ginresp.Wrap(r.apiHandler.CreateSubscription))
apiv2.GET("/users/:uid/subscriptions/:sid", ginresp.Wrap(r.apiHandler.GetSubscription)) apiv2.GET("/users/:uid/subscriptions/:sid", ginresp.Wrap(r.apiHandler.GetSubscription))
apiv2.DELETE("/users/:uid/subscriptions/:sid", ginresp.Wrap(r.apiHandler.CancelSubscription)) apiv2.DELETE("/users/:uid/subscriptions/:sid", ginresp.Wrap(r.apiHandler.CancelSubscription))
apiv2.POST("/users/:uid/subscriptions", ginresp.Wrap(r.apiHandler.CreateSubscription)) apiv2.PATCH("/users/:uid/subscriptions/:sid", ginresp.Wrap(r.apiHandler.UpdateSubscription))
apiv2.PATCH("/users/:uid/subscriptions", ginresp.Wrap(r.apiHandler.UpdateSubscription))
apiv2.GET("/messages", ginresp.Wrap(r.apiHandler.ListMessages)) apiv2.GET("/messages", ginresp.Wrap(r.apiHandler.ListMessages))
apiv2.GET("/messages/:mid", ginresp.Wrap(r.apiHandler.GetMessage)) apiv2.GET("/messages/:mid", ginresp.Wrap(r.apiHandler.GetMessage))

View File

@ -175,11 +175,11 @@ func (db *Database) ListChannelsByAccess(ctx TxContext, userid models.UserID, co
return nil, err return nil, err
} }
confCond := "" confCond := "OR (sub.subscription_id IS NOT NULL)"
if confirmed != nil && *confirmed { if confirmed != nil && *confirmed {
confCond = "OR sub.confirmed = 1" confCond = "OR (sub.subscription_id IS NOT NULL AND sub.confirmed = 1)"
} else if confirmed != nil && !*confirmed { } else if confirmed != nil && !*confirmed {
confCond = "OR sub.confirmed = 0" confCond = "OR (sub.subscription_id IS NOT NULL AND sub.confirmed = 0)"
} }
rows, err := tx.Query(ctx, "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE owner_user_id = :ouid "+confCond, sq.PP{ rows, err := tx.Query(ctx, "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE owner_user_id = :ouid "+confCond, sq.PP{

View File

@ -257,7 +257,7 @@
"tags": [ "tags": [
"Common" "Common"
], ],
"summary": "Check for a wroking database connection", "summary": "Check for a working database connection",
"operationId": "api-common-dbtest", "operationId": "api-common-dbtest",
"responses": { "responses": {
"200": { "200": {
@ -1742,7 +1742,7 @@
}, },
"/api/users/{uid}/subscriptions": { "/api/users/{uid}/subscriptions": {
"get": { "get": {
"description": "The possible values for 'selector' are:\n- \"owner_all\" All subscriptions (confirmed/unconfirmed) with the user as owner (= subscriptions he can use to read channels)\n- \"owner_confirmed\" Confirmed subscriptions with the user as owner\n- \"owner_unconfirmed\" Unconfirmed (Pending) subscriptions with the user as owner\n- \"incoming_all\" All subscriptions (confirmed/unconfirmed) from other users to channels of this user (= incoming subscriptions and subscription requests)\n- \"incoming_confirmed\" Confirmed subscriptions from other users to channels of this user\n- \"incoming_unconfirmed\" Unconfirmed subscriptions from other users to channels of this user (= requests)", "description": "The possible values for 'selector' are:\n- \"outgoing_all\" All subscriptions (confirmed/unconfirmed) with the user as subscriber (= subscriptions he can use to read channels)\n- \"outgoing_confirmed\" Confirmed subscriptions with the user as subscriber\n- \"outgoing_unconfirmed\" Unconfirmed (Pending) subscriptions with the user as subscriber\n- \"incoming_all\" All subscriptions (confirmed/unconfirmed) from other users to channels of this user (= incoming subscriptions and subscription requests)\n- \"incoming_confirmed\" Confirmed subscriptions from other users to channels of this user\n- \"incoming_unconfirmed\" Unconfirmed subscriptions from other users to channels of this user (= requests)",
"tags": [ "tags": [
"API-v2" "API-v2"
], ],
@ -1758,9 +1758,9 @@
}, },
{ {
"enum": [ "enum": [
"owner_all", "outgoing_all",
"owner_confirmed", "outgoing_confirmed",
"owner_unconfirmed", "outgoing_unconfirmed",
"incoming_all", "incoming_all",
"incoming_confirmed", "incoming_confirmed",
"incoming_unconfirmed" "incoming_unconfirmed"
@ -1987,6 +1987,14 @@
"name": "sid", "name": "sid",
"in": "path", "in": "path",
"required": true "required": true
},
{
"description": " ",
"name": "post_data",
"in": "body",
"schema": {
"$ref": "#/definitions/handler.UpdateSubscription.body"
}
} }
], ],
"responses": { "responses": {
@ -2410,11 +2418,6 @@
}, },
"handler.CreateSubscription.body": { "handler.CreateSubscription.body": {
"type": "object", "type": "object",
"required": [
"channel_id",
"channel_internal_name",
"channel_owner_user_id"
],
"properties": { "properties": {
"channel_id": { "channel_id": {
"type": "integer" "type": "integer"
@ -2738,6 +2741,14 @@
} }
} }
}, },
"handler.UpdateSubscription.body": {
"type": "object",
"properties": {
"confirmed": {
"type": "boolean"
}
}
},
"handler.Upgrade.response": { "handler.Upgrade.response": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -66,10 +66,6 @@ definitions:
type: string type: string
channel_owner_user_id: channel_owner_user_id:
type: integer type: integer
required:
- channel_id
- channel_internal_name
- channel_owner_user_id
type: object type: object
handler.CreateUser.body: handler.CreateUser.body:
properties: properties:
@ -277,6 +273,11 @@ definitions:
user_key: user_key:
type: string type: string
type: object type: object
handler.UpdateSubscription.body:
properties:
confirmed:
type: boolean
type: object
handler.Upgrade.response: handler.Upgrade.response:
properties: properties:
is_pro: is_pro:
@ -695,7 +696,7 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/ginresp.apiError' $ref: '#/definitions/ginresp.apiError'
summary: Check for a wroking database connection summary: Check for a working database connection
tags: tags:
- Common - Common
/api/expand.php: /api/expand.php:
@ -1701,9 +1702,9 @@ paths:
get: get:
description: |- description: |-
The possible values for 'selector' are: The possible values for 'selector' are:
- "owner_all" All subscriptions (confirmed/unconfirmed) with the user as owner (= subscriptions he can use to read channels) - "outgoing_all" All subscriptions (confirmed/unconfirmed) with the user as subscriber (= subscriptions he can use to read channels)
- "owner_confirmed" Confirmed subscriptions with the user as owner - "outgoing_confirmed" Confirmed subscriptions with the user as subscriber
- "owner_unconfirmed" Unconfirmed (Pending) subscriptions with the user as owner - "outgoing_unconfirmed" Unconfirmed (Pending) subscriptions with the user as subscriber
- "incoming_all" All subscriptions (confirmed/unconfirmed) from other users to channels of this user (= incoming subscriptions and subscription requests) - "incoming_all" All subscriptions (confirmed/unconfirmed) from other users to channels of this user (= incoming subscriptions and subscription requests)
- "incoming_confirmed" Confirmed subscriptions from other users to channels of this user - "incoming_confirmed" Confirmed subscriptions from other users to channels of this user
- "incoming_unconfirmed" Unconfirmed subscriptions from other users to channels of this user (= requests) - "incoming_unconfirmed" Unconfirmed subscriptions from other users to channels of this user (= requests)
@ -1716,9 +1717,9 @@ paths:
type: integer type: integer
- description: 'Filter subscriptions (default: owner_all)' - description: 'Filter subscriptions (default: owner_all)'
enum: enum:
- owner_all - outgoing_all
- owner_confirmed - outgoing_confirmed
- owner_unconfirmed - outgoing_unconfirmed
- incoming_all - incoming_all
- incoming_confirmed - incoming_confirmed
- incoming_unconfirmed - incoming_unconfirmed
@ -1872,6 +1873,11 @@ paths:
name: sid name: sid
required: true required: true
type: integer type: integer
- description: ' '
in: body
name: post_data
schema:
$ref: '#/definitions/handler.UpdateSubscription.body'
responses: responses:
"200": "200":
description: OK description: OK

View File

@ -150,6 +150,41 @@ func TestChannelNameNormalization(t *testing.T) {
} }
func TestListChannelsDefault(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
data := tt.InitDefaultData(t, ws)
type chanlist struct {
Channels []gin.H `json:"channels"`
}
testdata := map[int][]string{
0: {"main", "chatting chamber", "unicôdé häll \U0001f92a", "promotions", "reminders"},
1: {"main", "private"},
2: {"main", "ü", "ö", "ä"},
3: {"main", "\U0001f5ff", "innovations", "reminders"},
4: {"main"},
5: {"main", "test1", "test2", "test3", "test4", "test5"},
6: {"main", "security", "lipsum"},
7: {"main"},
8: {"main"},
9: {"main", "manual@chan"},
10: {"main"},
11: {"promotions"},
12: {},
13: {},
14: {"main", "chan_self_subscribed", "chan_self_unsub"},
15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"},
}
for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", data.User[k].UID))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
}
}
func TestListChannelsOwned(t *testing.T) { func TestListChannelsOwned(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t) ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop() defer stop()
@ -175,12 +210,12 @@ func TestListChannelsOwned(t *testing.T) {
11: {"promotions"}, 11: {"promotions"},
12: {}, 12: {},
13: {}, 13: {},
14: {"main", "chan_self_subscribed", "chan_self_unsub"}, //TODO these two have the interesting cases 14: {"main", "chan_self_subscribed", "chan_self_unsub"},
15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"}, //TODO these two have the interesting cases 15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"},
} }
for k, v := range testdata { for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", data.User[k].UID)) r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=%s", data.User[k].UID, "owned"))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name") tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
} }
} }
@ -210,12 +245,12 @@ func TestListChannelsSubscribedAny(t *testing.T) {
11: {"promotions"}, 11: {"promotions"},
12: {}, 12: {},
13: {}, 13: {},
14: {"main", "chan_self_subscribed", "chan_self_unsub"}, //TODO these two have the interesting cases 14: {"main", "chan_self_subscribed", "chan_other_request", "chan_other_accepted"},
15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"}, //TODO these two have the interesting cases 15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"},
} }
for k, v := range testdata { for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", data.User[k].UID)) r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=%s", data.User[k].UID, "subscribed_any"))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name") tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
} }
} }
@ -245,12 +280,12 @@ func TestListChannelsAllAny(t *testing.T) {
11: {"promotions"}, 11: {"promotions"},
12: {}, 12: {},
13: {}, 13: {},
14: {"main", "chan_self_subscribed", "chan_self_unsub"}, //TODO these two have the interesting cases 14: {"main", "chan_self_subscribed", "chan_self_unsub", "chan_other_request", "chan_other_accepted"},
15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"}, //TODO these two have the interesting cases 15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"},
} }
for k, v := range testdata { for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", data.User[k].UID)) r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=%s", data.User[k].UID, "all_any"))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name") tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
} }
} }
@ -280,12 +315,12 @@ func TestListChannelsSubscribed(t *testing.T) {
11: {"promotions"}, 11: {"promotions"},
12: {}, 12: {},
13: {}, 13: {},
14: {"main", "chan_self_subscribed", "chan_self_unsub"}, //TODO these two have the interesting cases 14: {"main", "chan_self_subscribed", "chan_other_accepted"},
15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"}, //TODO these two have the interesting cases 15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"},
} }
for k, v := range testdata { for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", data.User[k].UID)) r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=%s", data.User[k].UID, "subscribed"))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name") tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
} }
} }
@ -315,12 +350,12 @@ func TestListChannelsAll(t *testing.T) {
11: {"promotions"}, 11: {"promotions"},
12: {}, 12: {},
13: {}, 13: {},
14: {"main", "chan_self_subscribed", "chan_self_unsub"}, //TODO these two have the interesting cases 14: {"main", "chan_self_subscribed", "chan_self_unsub", "chan_other_accepted"},
15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"}, //TODO these two have the interesting cases 15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"},
} }
for k, v := range testdata { for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", data.User[k].UID)) r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=%s", data.User[k].UID, "all"))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name") tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
} }
} }

View File

@ -463,7 +463,9 @@ func doAcceptSub(t *testing.T, baseUrl string, user Userdat, subscriber Userdat,
} }
} }
RequestAuthDelete[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions/%v", user.UID, subdat["subscription_id"]), gin.H{}) RequestAuthPatch[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions/%v", user.UID, subdat["subscription_id"]), gin.H{
"confirmed": true,
})
} }