mirror of
https://github.com/hanzoai/team.git
synced 2026-10-30 14:49:35 +00:00
* Add CreatedBy and UpdatedBy to the properties fields and values (#34485) * Add CreatedBy and UpdatedBy to the properties fields and values * Fix types --------- Co-authored-by: Miguel de la Cruz <[email protected]> * Adds ObjectType to the property fields table (#34908) Co-authored-by: Miguel de la Cruz <[email protected]> * Update ObjectType migration setting an empty value and marking the column as not null (#34915) Co-authored-by: Miguel de la Cruz <[email protected]> * Adds uniqueness mechanisms to the property fields (#35058) * Adds uniqueness mechanisms to the property fields After adding ObjectType, this commit ensures that both the PSAv1 and PSAv2 schemas are supported, and enforces property uniqueness through both database indexes and a logical check when creating new property fields. * Adds uniqueness check to property updates Updates are covered on this commit and we refactor as well the SQL code to use the squirrel builder and work better with the conditional addition of the `existingID` piece of the query. * Add translations to error messages * Fixing retrylayer mocks * Remove retrylayer duplication * Address review comments * Fix comment to avoid linter issues * Address PR comments * Update server/channels/db/migrations/postgres/000157_add_object_type_to_property_fields.down.sql Co-authored-by: Alejandro García Montoro <[email protected]> * Update server/channels/db/migrations/postgres/000157_add_object_type_to_property_fields.up.sql Co-authored-by: Alejandro García Montoro <[email protected]> * Update server/channels/db/migrations/postgres/000157_add_object_type_to_property_fields.up.sql Co-authored-by: Alejandro García Montoro <[email protected]> * Update field validation to check only for valid target types * Update migrations to avoid concurrent index creation within a transaction * Update migrations to make all index ops concurrent * Update tests to use valid PSAv2 property fields * Adds a helper for valid PSAv2 TargetTypes --------- Co-authored-by: Miguel de la Cruz <[email protected]> Co-authored-by: Alejandro García Montoro <[email protected]> * Fix property tests (#35388) Co-authored-by: Miguel de la Cruz <[email protected]> * Adds Integrated Boards feature flag (#35378) Co-authored-by: Miguel de la Cruz <[email protected]> * Adds Integrated Boards MVP API changes (#34822) This PR includes the necessary changes for channels and posts endpoints and adds a set of generic endpoints to retrieve and manage property fields and values following the new Property System approach. Co-authored-by: Miguel de la Cruz <[email protected]> Co-authored-by: Mattermost Build <[email protected]> * Property System Architecture permissions for v2 (#35113) * Adds uniqueness mechanisms to the property fields After adding ObjectType, this commit ensures that both the PSAv1 and PSAv2 schemas are supported, and enforces property uniqueness through both database indexes and a logical check when creating new property fields. * Adds uniqueness check to property updates Updates are covered on this commit and we refactor as well the SQL code to use the squirrel builder and work better with the conditional addition of the `existingID` piece of the query. * Add translations to error messages * Add the permissions to the migrations, model and update the store calls * Adds the property field and property group app layer * Adds authorization helpers for property fields and values * Make sure that users cannot lock themselves out of property fields * Migrate permissions from a JSON column to three normalized columns * Remove the audit comment * Use target level constants in authorization * Log authorization membership failures * Rename admin to sysadmin * Fix i18n sorting --------- Co-authored-by: Miguel de la Cruz <[email protected]> * Add Views store and app layer (#35361) * Add Views store and app layer for Integrated Boards Implements the View entity (model, SQL store, service, app) as described in the Integrated Boards tech spec. Views are channel-scoped board configurations with typed props (board, kanban subviews) and soft-delete. - public/model: View, ViewBoardProps, Subview, ViewPatch types with PreSave/PreUpdate/IsValid/Patch/Clone/Auditable - Migration 158: Views table with jsonb Props column and indexes - SqlViewStore: CRUD with nil-safe Props marshaling (AppendBinaryFlag) - ViewService: CreateView seeds default kanban subview and links the boards property field; caches boardPropertyFieldID at startup - App layer: CreateView/GetView/GetViewsForChannel/UpdateView/DeleteView with channel-membership permission checks and WebSocket events (view_created, view_updated, view_deleted) - doSetupBoardsPropertyField: registers the Boards property group and board field in NewServer() before ViewService construction - GetFieldByName now returns store.ErrNotFound instead of raw sql.ErrNoRows * Move permission checks out of App layer for views - Remove HasPermissionToChannel calls from all App view methods - Drop userID params from GetView, GetViewsForChannel, UpdateView, DeleteView - Fix doSetupBoardsPropertyField to include required TargetType for PSAv2 field * Make View service generic and enforce board validation in model - Remove board-specific auto-setup from service and server startup - Enforce that board views require Props, at least one subview, and at least one linked property in IsValid() - Move default subview seeding out of app layer; callers must provide valid props - Call PreSave on subviews during PreUpdate to assign IDs to new subviews - Update all tests to reflect the new validation requirements * Restore migrations files to match base branch * Distinguish ErrNotFound from other errors in view store Get * Use CONCURRENTLY and nontransactional for index operations in views migration * Split views index creation into separate nontransactional migrations * Update migrations.list * Update i18n translations for views * Fix makeView helper to include required Props for board view validation * Rename ctx parameter from c to rctx in OAuthProvider mock * Remove views service layer, call store directly from app * Return 500 for unexpected DB errors in GetView, 404 only for not-found * Harden View model: deep-copy Props, validate linked property IDs - Add ViewBoardProps.Clone() to deep-copy LinkedProperties and Subviews - Use it in View.Clone() and View.Patch() to prevent shared-slice aliasing - Iterate over LinkedProperties in View.IsValid() and reject invalid IDs with a dedicated i18n key - Register ViewStore in storetest AssertExpectations so mock expectations are enforced - Add tests covering all new behaviours * Restore autotranslation worker_stopped i18n translation * Fix view store test IDs and improve error handling in app layer - Use model.NewId() for linked property IDs in testUpdateView to fix validation failure (IsValid rejects non-UUID strings) - Fix import grouping in app/view.go (stdlib imports in one block) - Return 404 instead of 500 when Update/Delete store calls return ErrNotFound (e.g. concurrent deletion TOCTOU race) * Add View store mock to retrylayer test genStore helper The View store was added to the store interface but the genStore() helper in retrylayer_test.go was not updated, causing TestRetry to panic. Also removes the duplicate Recap mock registration. * Refactor view deletion and websocket event handling; update SQL store methods to use query builder * revert property field store * Remove useless migrations * Add cursor-based pagination to View store GetForChannel - Add ViewQueryCursor and ViewQueryOpts types with validation - Return (views, cursor, error) for caller-driven pagination - PerPage clamping: <=0 defaults to 20, >200 clamps to 200 - Support IncludeDeleted filter - Add comprehensive store tests for pagination, cursor edge cases, PerPage clamping, and invalid input rejection - Add app layer test for empty channelID → 400 - Update interface, retrylayer, timerlayer, and mock signatures * Refactor test loops in ViewStore tests for improved readability * change pagination to limit/offset * Add upper-bound limits on View Subviews and LinkedProperties Defense-in-depth validation: cap Subviews at 50 and LinkedProperties at 500 to prevent abuse below the 300KB payload limit. --------- * MM-67388, MM-66528, MM-67750: Add View REST API endpoints, websocket events, and sort order (#35442) * Add Views store and app layer for Integrated Boards Implements the View entity (model, SQL store, service, app) as described in the Integrated Boards tech spec. Views are channel-scoped board configurations with typed props (board, kanban subviews) and soft-delete. - public/model: View, ViewBoardProps, Subview, ViewPatch types with PreSave/PreUpdate/IsValid/Patch/Clone/Auditable - Migration 158: Views table with jsonb Props column and indexes - SqlViewStore: CRUD with nil-safe Props marshaling (AppendBinaryFlag) - ViewService: CreateView seeds default kanban subview and links the boards property field; caches boardPropertyFieldID at startup - App layer: CreateView/GetView/GetViewsForChannel/UpdateView/DeleteView with channel-membership permission checks and WebSocket events (view_created, view_updated, view_deleted) - doSetupBoardsPropertyField: registers the Boards property group and board field in NewServer() before ViewService construction - GetFieldByName now returns store.ErrNotFound instead of raw sql.ErrNoRows * Move permission checks out of App layer for views - Remove HasPermissionToChannel calls from all App view methods - Drop userID params from GetView, GetViewsForChannel, UpdateView, DeleteView - Fix doSetupBoardsPropertyField to include required TargetType for PSAv2 field * Make View service generic and enforce board validation in model - Remove board-specific auto-setup from service and server startup - Enforce that board views require Props, at least one subview, and at least one linked property in IsValid() - Move default subview seeding out of app layer; callers must provide valid props - Call PreSave on subviews during PreUpdate to assign IDs to new subviews - Update all tests to reflect the new validation requirements * Restore migrations files to match base branch * Distinguish ErrNotFound from other errors in view store Get * Use CONCURRENTLY and nontransactional for index operations in views migration * Split views index creation into separate nontransactional migrations * Update migrations.list * Update i18n translations for views * Fix makeView helper to include required Props for board view validation * Rename ctx parameter from c to rctx in OAuthProvider mock * Remove views service layer, call store directly from app * Return 500 for unexpected DB errors in GetView, 404 only for not-found * Harden View model: deep-copy Props, validate linked property IDs - Add ViewBoardProps.Clone() to deep-copy LinkedProperties and Subviews - Use it in View.Clone() and View.Patch() to prevent shared-slice aliasing - Iterate over LinkedProperties in View.IsValid() and reject invalid IDs with a dedicated i18n key - Register ViewStore in storetest AssertExpectations so mock expectations are enforced - Add tests covering all new behaviours * Restore autotranslation worker_stopped i18n translation * Fix view store test IDs and improve error handling in app layer - Use model.NewId() for linked property IDs in testUpdateView to fix validation failure (IsValid rejects non-UUID strings) - Fix import grouping in app/view.go (stdlib imports in one block) - Return 404 instead of 500 when Update/Delete store calls return ErrNotFound (e.g. concurrent deletion TOCTOU race) * Add View store mock to retrylayer test genStore helper The View store was added to the store interface but the genStore() helper in retrylayer_test.go was not updated, causing TestRetry to panic. Also removes the duplicate Recap mock registration. * Refactor view deletion and websocket event handling; update SQL store methods to use query builder * revert property field store * Add View API endpoints with OpenAPI spec, client methods, and i18n Implement REST API for channel views (board-type) behind the IntegratedBoards feature flag. Adds CRUD endpoints under /api/v4/channels/{channel_id}/views with permission checks matching the channel bookmark pattern. * Remove useless migrations * Add cursor-based pagination to View store GetForChannel - Add ViewQueryCursor and ViewQueryOpts types with validation - Return (views, cursor, error) for caller-driven pagination - PerPage clamping: <=0 defaults to 20, >200 clamps to 200 - Support IncludeDeleted filter - Add comprehensive store tests for pagination, cursor edge cases, PerPage clamping, and invalid input rejection - Add app layer test for empty channelID → 400 - Update interface, retrylayer, timerlayer, and mock signatures * Add cursor-based pagination to View API for channel views * Enhance cursor handling in getViewsForChannel and update tests for pagination * Refactor test loops in ViewStore tests for improved readability * Refactor loop in TestGetViewsForChannel for improved readability * change pagination to limit/offset * switch to limit/offset pagination * Add upper-bound limits on View Subviews and LinkedProperties Defense-in-depth validation: cap Subviews at 50 and LinkedProperties at 500 to prevent abuse below the 300KB payload limit. * Add view sort order API endpoint Add POST /api/v4/channels/{channel_id}/views/{view_id}/sort_order endpoint following the channel bookmarks reorder pattern. Includes store, app, and API layers with full test coverage at each layer. * Add connectionId to view WebSocket events and sort_order API spec Thread connectionId from request header through all view handlers (create, update, delete, sort_order) to WebSocket events, matching the channel bookmarks pattern. Add sort_order endpoint to OpenAPI spec. Update minimum server version to 11.6. * Remove duplicate View/ViewPatch definitions from definitions.yaml The merge from integrated-boards-mvp introduced duplicate View and ViewPatch schema definitions that were already defined earlier in the file with more detail (including ViewBoardProps ref and enums). * Update minimum server version to 11.6 in views API spec * Add missing translations for view sort order error messages * Merge integrated-boards-mvp into ibmvp_api-views; remove spec files * Fix flaky TestViewStore timestamp test on CI Add sleep before UpdateSortOrder to ensure timestamps differ, preventing same-millisecond comparisons on fast CI machines. * remove duplicate views.yaml imclude * Use c.boolString() for include_deleted query param in GetViewsForChannel * Fix views.yaml sort order schema: use integer type and require body * Refactor view sort order tests to use named IDs instead of array indices Extract idA/idB/idC from views slice and add BEFORE/AFTER comments to make stateful subtest ordering easier to follow. * Return 404 instead of 403 for view operations on deleted channels Deleted channels should appear non-existent to callers rather than revealing their existence via a 403. Detailed error text explains the context for debugging. * add missing channel deleteat checks * Use c.Params.Page instead of manual page query param parsing in getViewsForChannel c.Params already validates and defaults page/per_page, so the manual parsing was redundant. * Add support for total count in views retrieval * Add tests for handling deleted views in GetViewsForChannel and GetView * Short-circuit negative newIndex in UpdateSortOrder before opening transaction * Add per-channel limit on views to bound UpdateSortOrder cost Without a cap, unbounded view creation makes sort-order updates increasingly expensive (CASE WHEN per view, row locks). Adds MaxViewsPerChannel=50 constant and enforces it in the app layer before saving. Includes API and app layer tests. * Remove include_deleted support from views API Soft-deleted views are structural metadata with low risk, but no other similar endpoint (e.g. channel bookmarks) exposes deleted records without an admin gate. Rather than adding an admin-only permission check for consistency, remove the feature entirely since there is no current use case. * Update view permissions to require `create_post` instead of channel management permissions * Remove obsolete view management error messages for direct and group messages --------- * feat(migrations): add user tracking and object type to property fields - Introduced user tracking columns (CreatedBy, UpdatedBy) to PropertyFields and PropertyValues. - Added ObjectType column to PropertyFields with associated unique indexes for legacy and typed properties. - Created new migration scripts for adding and dropping these features, including necessary indexes for data integrity. - Established views for managing property fields with new attributes. This update enhances the schema to support better tracking and categorization of property fields. * Add Property System Architecture v2 API endpoints (#35583) * Adds uniqueness mechanisms to the property fields After adding ObjectType, this commit ensures that both the PSAv1 and PSAv2 schemas are supported, and enforces property uniqueness through both database indexes and a logical check when creating new property fields. * Adds uniqueness check to property updates Updates are covered on this commit and we refactor as well the SQL code to use the squirrel builder and work better with the conditional addition of the `existingID` piece of the query. * Add translations to error messages * Add the permissions to the migrations, model and update the store calls * Adds the property field and property group app layer * Adds authorization helpers for property fields and values * Make sure that users cannot lock themselves out of property fields * Migrate permissions from a JSON column to three normalized columns * Remove the audit comment * Use target level constants in authorization * Log authorization membership failures * Rename admin to sysadmin * Adds the Property System Architecture v2 API endpoints * Adds permission checks to the create field endpoint * Add target access checks to value endpoints * Add default branches for object_type and target_type and extra guards for cursor client4 methods * Fix vet API mismatch * Fix error checks * Fix linter * Add merge semantics for property patch logic and API endpoint * Fix i18n * Fix duplicated patch elements and early return on bad cursor * Update docs to use enums * Fix i18n sorting * Update app layer to return model.AppError * Adds a limit to the number of property values that can be patched in the same request * Require target_type filter when searching property fields * Add objectType validation as part of field.IsValid() * Fix linter * Fix test with bad objecttpye * Fix test grouping --------- Co-authored-by: Miguel de la Cruz <[email protected]> * MM-67968: Flatten view model — remove icon, subviews, typed board props (#35726) * feat(views): flatten view model by removing icon, subview, and board props Simplifies the View data model as part of MM-67968: removes Icon, Subview, and ViewBoardProps types; renames ViewTypeBoard to ViewTypeKanban; replaces typed Props with StringInterface (map[string]any); adds migration 000167 to drop the Icon column from the Views table. * feat(api): update views OpenAPI spec to reflect flattened model Removes ViewBoardProps, Subview, and icon from the View and ViewPatch schemas. Changes type enum from board to kanban. Replaces typed props with a free-form StringInterface object. Aligns with MM-67968. * refactor(views): simplify store by dropping dbView and marshalViewProps StringInterface already implements driver.Valuer and sql.Scanner, so the manual JSON marshal/unmarshal and the dbView intermediate struct were redundant. model.View now scans directly from the database. Also removes the dead ViewMaxLinkedProperties constant and wraps the Commit() error in UpdateSortOrder. * fix(api): allow arbitrary JSON in view props OpenAPI schema The props field was restricted to string values via additionalProperties: { type: string }, conflicting with the Go model's StringInterface (map[string]any). Changed to additionalProperties: true in View, ViewPatch, and inline POST schemas. --------- * Adds basic implementation of the generic redux store for PSAv2 (#35512) * Adds basic implementation of the generic redux store for PSAv2 * Add created_by and updated_by to the test fixtures * Make target_id, target_type and object_type mandatory * Wrap getPropertyFieldsByIds and getPropertyValuesForTargetByFieldIds with createSelector * Address PR comments --------- Co-authored-by: Miguel de la Cruz <[email protected]> * Adds websocket messages for the PSAv2 API events (#35696) * Adds uniqueness mechanisms to the property fields After adding ObjectType, this commit ensures that both the PSAv1 and PSAv2 schemas are supported, and enforces property uniqueness through both database indexes and a logical check when creating new property fields. * Adds uniqueness check to property updates Updates are covered on this commit and we refactor as well the SQL code to use the squirrel builder and work better with the conditional addition of the `existingID` piece of the query. * Add translations to error messages * Add the permissions to the migrations, model and update the store calls * Adds the property field and property group app layer * Adds authorization helpers for property fields and values * Make sure that users cannot lock themselves out of property fields * Migrate permissions from a JSON column to three normalized columns * Remove the audit comment * Use target level constants in authorization * Log authorization membership failures * Rename admin to sysadmin * Adds the Property System Architecture v2 API endpoints * Adds permission checks to the create field endpoint * Add target access checks to value endpoints * Add default branches for object_type and target_type and extra guards for cursor client4 methods * Fix vet API mismatch * Fix error checks * Fix linter * Add merge semantics for property patch logic and API endpoint * Fix i18n * Fix duplicated patch elements and early return on bad cursor * Update docs to use enums * Fix i18n sorting * Update app layer to return model.AppError * Adds a limit to the number of property values that can be patched in the same request * Adds websocket messages for the PSAv2 API events * Add IsPSAv2 helper to the property field for clarity * Add guard against nil returns on field deletion * Add docs to the websocket endpoints --------- Co-authored-by: Miguel de la Cruz <[email protected]> * migrations: consolidate views migrations and reorder after master - Merged 000165 (create Views) with 000167 (drop Icon) since Icon was never needed - Renumbered branch migrations 159-166 → 160-167 so master's 000159 (deduplicate_policy_names) runs first - Regenerated migrations.list * Add API endpoint to retrieve posts for a specific view (#35604) Automatic Merge * Apply fixes after merge * Return a more specific error from getting multiple fields * Prevent getting broadcast params on field deletion if not needed * Remove duplicated migration code * Update property conflict code to always use master * Adds nil guard when iterating on property fields * Check that permission level is valid before getting rejected by the database * Validate correctness on TargetID for PSAv2 fields * Avoid PSAv1 using permissions or protected * Fix test data after validation change * Fix flaky search test * Adds more posts for filter use cases to properly test exclusions --------- Co-authored-by: Miguel de la Cruz <[email protected]> Co-authored-by: Alejandro García Montoro <[email protected]> Co-authored-by: Julien Tant <[email protected]> Co-authored-by: Mattermost Build <[email protected]> Co-authored-by: Julien Tant <[email protected]> Co-authored-by: Hanzo Dev <[email protected]>
6372 lines
199 KiB
Go
6372 lines
199 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package storetest
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
|
)
|
|
|
|
func TestPostStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
t.Run("SaveMultiple", func(t *testing.T) { testPostStoreSaveMultiple(t, rctx, ss) })
|
|
t.Run("Save", func(t *testing.T) { testPostStoreSave(t, rctx, ss) })
|
|
t.Run("SaveAndUpdateChannelMsgCounts", func(t *testing.T) { testPostStoreSaveChannelMsgCounts(t, rctx, ss) })
|
|
t.Run("Get", func(t *testing.T) { testPostStoreGet(t, rctx, ss) })
|
|
t.Run("GetSingle", func(t *testing.T) { testPostStoreGetSingle(t, rctx, ss) })
|
|
t.Run("Update", func(t *testing.T) { testPostStoreUpdate(t, rctx, ss) })
|
|
t.Run("Delete", func(t *testing.T) { testPostStoreDelete(t, rctx, ss) })
|
|
t.Run("PermDelete1Level", func(t *testing.T) { testPostStorePermDelete1Level(t, rctx, ss) })
|
|
t.Run("PermDelete1Level2", func(t *testing.T) { testPostStorePermDelete1Level2(t, rctx, ss) })
|
|
t.Run("PermDeleteLimitExceeded", func(t *testing.T) { testPostStorePermDeleteLimitExceeded(t, rctx, ss) })
|
|
t.Run("GetWithChildren", func(t *testing.T) { testPostStoreGetWithChildren(t, rctx, ss) })
|
|
t.Run("GetPostsWithDetails", func(t *testing.T) { testPostStoreGetPostsWithDetails(t, rctx, ss) })
|
|
t.Run("GetPostsBeforeAfter", func(t *testing.T) { testPostStoreGetPostsBeforeAfter(t, rctx, ss) })
|
|
t.Run("GetPostsSince", func(t *testing.T) { testPostStoreGetPostsSince(t, rctx, ss) })
|
|
t.Run("GetPosts", func(t *testing.T) { testPostStoreGetPosts(t, rctx, ss) })
|
|
t.Run("GetPostBeforeAfter", func(t *testing.T) { testPostStoreGetPostBeforeAfter(t, rctx, ss) })
|
|
t.Run("UserCountsWithPostsByDay", func(t *testing.T) { testUserCountsWithPostsByDay(t, rctx, ss) })
|
|
t.Run("PostCountsByDuration", func(t *testing.T) { testPostCountsByDay(t, rctx, ss) })
|
|
t.Run("PostCounts", func(t *testing.T) { testPostCounts(t, rctx, ss) })
|
|
t.Run("GetFlaggedPostsForTeam", func(t *testing.T) { testPostStoreGetFlaggedPostsForTeam(t, rctx, ss, s) })
|
|
t.Run("GetFlaggedPosts", func(t *testing.T) { testPostStoreGetFlaggedPosts(t, rctx, ss) })
|
|
t.Run("GetFlaggedPostsForChannel", func(t *testing.T) { testPostStoreGetFlaggedPostsForChannel(t, rctx, ss) })
|
|
t.Run("GetPostsCreatedAt", func(t *testing.T) { testPostStoreGetPostsCreatedAt(t, rctx, ss) })
|
|
t.Run("Overwrite", func(t *testing.T) { testPostStoreOverwrite(t, rctx, ss) })
|
|
t.Run("OverwriteMultiple", func(t *testing.T) { testPostStoreOverwriteMultiple(t, rctx, ss) })
|
|
t.Run("GetPostsByIds", func(t *testing.T) { testPostStoreGetPostsByIds(t, rctx, ss) })
|
|
t.Run("GetPostsBatchForIndexing", func(t *testing.T) { testPostStoreGetPostsBatchForIndexing(t, rctx, ss) })
|
|
t.Run("PermanentDeleteBatch", func(t *testing.T) { testPostStorePermanentDeleteBatch(t, rctx, ss) })
|
|
t.Run("GetOldest", func(t *testing.T) { testPostStoreGetOldest(t, rctx, ss) })
|
|
t.Run("TestGetMaxPostSize", func(t *testing.T) { testGetMaxPostSize(t, rctx, ss) })
|
|
t.Run("GetParentsForExportAfter", func(t *testing.T) { testPostStoreGetParentsForExportAfter(t, rctx, ss) })
|
|
t.Run("GetRepliesForExport", func(t *testing.T) { testPostStoreGetRepliesForExport(t, rctx, ss) })
|
|
t.Run("GetDirectPostParentsForExportAfter", func(t *testing.T) { testPostStoreGetDirectPostParentsForExportAfter(t, rctx, ss, s) })
|
|
t.Run("GetDirectPostParentsForExportAfterDeleted", func(t *testing.T) { testPostStoreGetDirectPostParentsForExportAfterDeleted(t, rctx, ss, s) })
|
|
t.Run("GetDirectPostParentsForExportAfterBatched", func(t *testing.T) { testPostStoreGetDirectPostParentsForExportAfterBatched(t, rctx, ss, s) })
|
|
t.Run("GetForThread", func(t *testing.T) { testPostStoreGetForThread(t, rctx, ss) })
|
|
t.Run("HasAutoResponsePostByUserSince", func(t *testing.T) { testHasAutoResponsePostByUserSince(t, rctx, ss) })
|
|
t.Run("GetPostsSinceUpdateForSync", func(t *testing.T) { testGetPostsSinceUpdateForSync(t, rctx, ss, s) })
|
|
t.Run("GetPostsSinceCreateForSync", func(t *testing.T) { testGetPostsSinceCreateForSync(t, rctx, ss, s) })
|
|
t.Run("GetPostsSinceForSyncExcludeMetadata", func(t *testing.T) { testGetPostsSinceForSyncExcludeMetadata(t, rctx, ss, s) })
|
|
t.Run("GetPostsSinceForSyncExcludedPostTypes", func(t *testing.T) { testGetPostsSinceForSyncExcludedPostTypes(t, rctx, ss, s) })
|
|
t.Run("GetPostsForReporting", func(t *testing.T) { testGetPostsForReporting(t, rctx, ss, s) })
|
|
t.Run("SetPostReminder", func(t *testing.T) { testSetPostReminder(t, rctx, ss, s) })
|
|
t.Run("GetPostReminders", func(t *testing.T) { testGetPostReminders(t, rctx, ss, s) })
|
|
t.Run("GetPostReminderMetadata", func(t *testing.T) { testGetPostReminderMetadata(t, rctx, ss, s) })
|
|
t.Run("DeleteAllPostRemindersForPost", func(t *testing.T) { testDeleteAllPostRemindersForPost(t, rctx, ss, s) })
|
|
t.Run("GetNthRecentPostTime", func(t *testing.T) { testGetNthRecentPostTime(t, rctx, ss) })
|
|
t.Run("GetEditHistoryForPost", func(t *testing.T) { testGetEditHistoryForPost(t, rctx, ss) })
|
|
t.Run("RestoreContentFlaggedPost", func(t *testing.T) { testRestoreContentFlaggedPost(t, rctx, ss) })
|
|
}
|
|
|
|
func testPostStoreSave(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t.Run("Save post", func(t *testing.T) {
|
|
o1 := model.Post{}
|
|
o1.ChannelId = model.NewId()
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
|
|
p, err := ss.Post().Save(rctx, &o1)
|
|
require.NoError(t, err, "couldn't save item")
|
|
assert.Equal(t, int64(0), p.ReplyCount)
|
|
})
|
|
|
|
t.Run("Save replies", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := model.Post{}
|
|
o1.ChannelId = channel1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.RootId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName2",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := model.Post{}
|
|
o2.ChannelId = channel2.Id
|
|
o2.UserId = model.NewId()
|
|
o2.RootId = o1.RootId
|
|
o2.Message = NewTestID()
|
|
|
|
channel3, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName3",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o3 := model.Post{}
|
|
o3.ChannelId = channel3.Id
|
|
o3.UserId = model.NewId()
|
|
o3.RootId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
|
|
p1, err := ss.Post().Save(rctx, &o1)
|
|
require.NoError(t, err, "couldn't save item")
|
|
assert.Equal(t, int64(1), p1.ReplyCount)
|
|
|
|
p2, err := ss.Post().Save(rctx, &o2)
|
|
require.NoError(t, err, "couldn't save item")
|
|
assert.Equal(t, int64(2), p2.ReplyCount)
|
|
|
|
p3, err := ss.Post().Save(rctx, &o3)
|
|
require.NoError(t, err, "couldn't save item")
|
|
assert.Equal(t, int64(1), p3.ReplyCount)
|
|
})
|
|
|
|
t.Run("Try to save existing post", func(t *testing.T) {
|
|
o1 := model.Post{}
|
|
o1.ChannelId = model.NewId()
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
|
|
_, err := ss.Post().Save(rctx, &o1)
|
|
require.NoError(t, err, "couldn't save item")
|
|
|
|
_, err = ss.Post().Save(rctx, &o1)
|
|
require.Error(t, err, "shouldn't be able to update from save")
|
|
})
|
|
|
|
t.Run("Update reply should update the UpdateAt of the root post", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
rootPost := model.Post{}
|
|
rootPost.ChannelId = channel.Id
|
|
rootPost.UserId = model.NewId()
|
|
rootPost.Message = NewTestID()
|
|
|
|
_, err = ss.Post().Save(rctx, &rootPost)
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
replyPost := model.Post{}
|
|
replyPost.ChannelId = rootPost.ChannelId
|
|
replyPost.UserId = model.NewId()
|
|
replyPost.Message = NewTestID()
|
|
replyPost.RootId = rootPost.Id
|
|
|
|
// We need to sleep here to be sure the post is not created during the same millisecond
|
|
time.Sleep(time.Millisecond)
|
|
_, err = ss.Post().Save(rctx, &replyPost)
|
|
require.NoError(t, err)
|
|
|
|
rrootPost, err := ss.Post().GetSingle(rctx, rootPost.Id, false)
|
|
require.NoError(t, err)
|
|
assert.Greater(t, rrootPost.UpdateAt, rootPost.UpdateAt)
|
|
})
|
|
|
|
t.Run("Create a post should update the channel LastPostAt and the total messages count by one", func(t *testing.T) {
|
|
channel := model.Channel{}
|
|
channel.Name = NewTestID()
|
|
channel.DisplayName = NewTestID()
|
|
channel.Type = model.ChannelTypeOpen
|
|
|
|
_, err := ss.Channel().Save(rctx, &channel, 100)
|
|
require.NoError(t, err)
|
|
|
|
post := model.Post{}
|
|
post.ChannelId = channel.Id
|
|
post.UserId = model.NewId()
|
|
post.Message = NewTestID()
|
|
|
|
// We need to sleep here to be sure the post is not created during the same millisecond
|
|
time.Sleep(time.Millisecond)
|
|
_, err = ss.Post().Save(rctx, &post)
|
|
require.NoError(t, err)
|
|
|
|
rchannel, err := ss.Channel().Get(channel.Id, false)
|
|
require.NoError(t, err)
|
|
assert.Greater(t, rchannel.LastPostAt, channel.LastPostAt)
|
|
assert.Equal(t, int64(1), rchannel.TotalMsgCount)
|
|
|
|
post = model.Post{}
|
|
post.ChannelId = channel.Id
|
|
post.UserId = model.NewId()
|
|
post.Message = NewTestID()
|
|
post.CreateAt = 5
|
|
|
|
// We need to sleep here to be sure the post is not created during the same millisecond
|
|
time.Sleep(time.Millisecond)
|
|
_, err = ss.Post().Save(rctx, &post)
|
|
require.NoError(t, err)
|
|
|
|
rchannel2, err := ss.Channel().Get(channel.Id, false)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, rchannel.LastPostAt, rchannel2.LastPostAt)
|
|
assert.Equal(t, int64(2), rchannel2.TotalMsgCount)
|
|
|
|
post = model.Post{}
|
|
post.ChannelId = channel.Id
|
|
post.UserId = model.NewId()
|
|
post.Message = NewTestID()
|
|
|
|
// We need to sleep here to be sure the post is not created during the same millisecond
|
|
time.Sleep(time.Millisecond)
|
|
_, err = ss.Post().Save(rctx, &post)
|
|
require.NoError(t, err)
|
|
|
|
rchannel3, err := ss.Channel().Get(channel.Id, false)
|
|
require.NoError(t, err)
|
|
assert.Greater(t, rchannel3.LastPostAt, rchannel2.LastPostAt)
|
|
assert.Equal(t, int64(3), rchannel3.TotalMsgCount)
|
|
})
|
|
|
|
t.Run("Save post with type card", func(t *testing.T) {
|
|
o1 := model.Post{}
|
|
o1.ChannelId = model.NewId()
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1.Type = model.PostTypeCard
|
|
|
|
p, err := ss.Post().Save(rctx, &o1)
|
|
require.NoError(t, err, "couldn't save item")
|
|
assert.Equal(t, model.PostTypeCard, p.Type)
|
|
assert.Equal(t, o1.Message, p.Message)
|
|
})
|
|
|
|
t.Run("Save post with priority metadata set", func(t *testing.T) {
|
|
o1 := model.Post{}
|
|
o1.ChannelId = model.NewId()
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
|
|
o1.Metadata = &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("important"),
|
|
RequestedAck: model.NewPointer(true),
|
|
PersistentNotifications: model.NewPointer(false),
|
|
},
|
|
}
|
|
|
|
p, err := ss.Post().Save(rctx, &o1)
|
|
require.NoError(t, err, "couldn't save item")
|
|
assert.Equal(t, int64(0), p.ReplyCount)
|
|
|
|
pp, err := ss.PostPriority().GetForPost(p.Id)
|
|
require.NoError(t, err, "couldn't save item")
|
|
assert.Equal(t, "important", *pp.Priority)
|
|
assert.Equal(t, true, *pp.RequestedAck)
|
|
assert.Equal(t, false, *pp.PersistentNotifications)
|
|
})
|
|
}
|
|
|
|
func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
p1 := model.Post{}
|
|
p1.ChannelId = model.NewId()
|
|
p1.UserId = model.NewId()
|
|
p1.Message = NewTestID()
|
|
|
|
p2 := model.Post{}
|
|
p2.ChannelId = model.NewId()
|
|
p2.UserId = model.NewId()
|
|
p2.Message = NewTestID()
|
|
|
|
p3 := model.Post{}
|
|
p3.ChannelId = model.NewId()
|
|
p3.UserId = model.NewId()
|
|
p3.Message = NewTestID()
|
|
|
|
p4 := model.Post{}
|
|
p4.ChannelId = model.NewId()
|
|
p4.UserId = model.NewId()
|
|
p4.Message = NewTestID()
|
|
|
|
t.Run("Save correctly a new set of posts", func(t *testing.T) {
|
|
newPosts, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3})
|
|
require.NoError(t, err)
|
|
require.Equal(t, -1, errIdx)
|
|
for _, post := range newPosts {
|
|
storedPost, err := ss.Post().GetSingle(rctx, post.Id, false)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, post.ChannelId, storedPost.ChannelId)
|
|
assert.Equal(t, post.Message, storedPost.Message)
|
|
assert.Equal(t, post.UserId, storedPost.UserId)
|
|
}
|
|
})
|
|
|
|
t.Run("Save replies", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName2",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channel3, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName3",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channel4, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName4",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
o1 := model.Post{}
|
|
o1.ChannelId = channel1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.RootId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
|
|
o2 := model.Post{}
|
|
o2.ChannelId = channel2.Id
|
|
o2.UserId = model.NewId()
|
|
o2.RootId = o1.RootId
|
|
o2.Message = NewTestID()
|
|
|
|
o3 := model.Post{}
|
|
o3.ChannelId = channel3.Id
|
|
o3.UserId = model.NewId()
|
|
o3.RootId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
|
|
o4 := model.Post{}
|
|
o4.ChannelId = channel4.Id
|
|
o4.UserId = model.NewId()
|
|
o4.Message = NewTestID()
|
|
|
|
newPosts, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&o1, &o2, &o3, &o4})
|
|
require.NoError(t, err, "couldn't save item")
|
|
require.Equal(t, -1, errIdx)
|
|
assert.Len(t, newPosts, 4)
|
|
assert.Equal(t, int64(2), newPosts[0].ReplyCount)
|
|
assert.Equal(t, int64(2), newPosts[1].ReplyCount)
|
|
assert.Equal(t, int64(1), newPosts[2].ReplyCount)
|
|
assert.Equal(t, int64(0), newPosts[3].ReplyCount)
|
|
})
|
|
|
|
t.Run("Try to save mixed, already saved and not saved posts", func(t *testing.T) {
|
|
newPosts, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p4, &p3})
|
|
require.Error(t, err)
|
|
require.Equal(t, 1, errIdx)
|
|
require.Nil(t, newPosts)
|
|
storedPost, err := ss.Post().GetSingle(rctx, p3.Id, false)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, p3.ChannelId, storedPost.ChannelId)
|
|
assert.Equal(t, p3.Message, storedPost.Message)
|
|
assert.Equal(t, p3.UserId, storedPost.UserId)
|
|
|
|
storedPost, err = ss.Post().GetSingle(rctx, p4.Id, false)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, storedPost)
|
|
})
|
|
|
|
t.Run("Update reply should update the UpdateAt of the root post", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
rootPost := model.Post{}
|
|
rootPost.ChannelId = channel.Id
|
|
rootPost.UserId = model.NewId()
|
|
rootPost.Message = NewTestID()
|
|
|
|
replyPost := model.Post{}
|
|
replyPost.ChannelId = rootPost.ChannelId
|
|
replyPost.UserId = model.NewId()
|
|
replyPost.Message = NewTestID()
|
|
replyPost.RootId = rootPost.Id
|
|
|
|
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&rootPost, &replyPost})
|
|
require.NoError(t, err)
|
|
|
|
rrootPost, err := ss.Post().GetSingle(rctx, rootPost.Id, false)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, rrootPost.UpdateAt, rootPost.UpdateAt)
|
|
|
|
replyPost2 := model.Post{}
|
|
replyPost2.ChannelId = rootPost.ChannelId
|
|
replyPost2.UserId = model.NewId()
|
|
replyPost2.Message = NewTestID()
|
|
replyPost2.RootId = rootPost.Id
|
|
|
|
replyPost3 := model.Post{}
|
|
replyPost3.ChannelId = rootPost.ChannelId
|
|
replyPost3.UserId = model.NewId()
|
|
replyPost3.Message = NewTestID()
|
|
replyPost3.RootId = rootPost.Id
|
|
|
|
// Ensure update does not occur in the same timestamp as creation
|
|
time.Sleep(time.Millisecond)
|
|
|
|
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&replyPost2, &replyPost3})
|
|
require.NoError(t, err)
|
|
|
|
rrootPost2, err := ss.Post().GetSingle(rctx, rootPost.Id, false)
|
|
require.NoError(t, err)
|
|
assert.Greater(t, rrootPost2.UpdateAt, rrootPost.UpdateAt)
|
|
})
|
|
|
|
t.Run("Create a post should update the channel LastPostAt and the total messages count by one", func(t *testing.T) {
|
|
channel := model.Channel{}
|
|
channel.Name = NewTestID()
|
|
channel.DisplayName = NewTestID()
|
|
channel.Type = model.ChannelTypeOpen
|
|
|
|
_, err := ss.Channel().Save(rctx, &channel, 100)
|
|
require.NoError(t, err)
|
|
|
|
post1 := model.Post{}
|
|
post1.ChannelId = channel.Id
|
|
post1.UserId = model.NewId()
|
|
post1.Message = NewTestID()
|
|
|
|
post2 := model.Post{}
|
|
post2.ChannelId = channel.Id
|
|
post2.UserId = model.NewId()
|
|
post2.Message = NewTestID()
|
|
post2.CreateAt = 5
|
|
|
|
post3 := model.Post{}
|
|
post3.ChannelId = channel.Id
|
|
post3.UserId = model.NewId()
|
|
post3.Message = NewTestID()
|
|
|
|
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&post1, &post2, &post3})
|
|
require.NoError(t, err)
|
|
|
|
rchannel, err := ss.Channel().Get(channel.Id, false)
|
|
require.NoError(t, err)
|
|
assert.Greater(t, rchannel.LastPostAt, channel.LastPostAt)
|
|
assert.Equal(t, int64(3), rchannel.TotalMsgCount)
|
|
})
|
|
|
|
t.Run("Thread participants", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := model.Post{}
|
|
o1.ChannelId = channel1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = "jessica hyde" + model.NewId() + "b"
|
|
|
|
root, err := ss.Post().Save(rctx, &o1)
|
|
require.NoError(t, err)
|
|
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName2",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channel3, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName3",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channel4, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName4",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channel5, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName5",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
o2 := model.Post{}
|
|
o2.ChannelId = channel2.Id
|
|
o2.UserId = model.NewId()
|
|
o2.RootId = root.Id
|
|
o2.Message = "zz" + model.NewId() + "b"
|
|
|
|
o3 := model.Post{}
|
|
o3.ChannelId = channel3.Id
|
|
o3.UserId = model.NewId()
|
|
o3.RootId = root.Id
|
|
o3.Message = "zz" + model.NewId() + "b"
|
|
|
|
o4 := model.Post{}
|
|
o4.ChannelId = channel4.Id
|
|
o4.UserId = o2.UserId
|
|
o4.RootId = root.Id
|
|
o4.Message = "zz" + model.NewId() + "b"
|
|
|
|
o5 := model.Post{}
|
|
o5.ChannelId = channel5.Id
|
|
o5.UserId = o1.UserId
|
|
o5.RootId = root.Id
|
|
o5.Message = "zz" + model.NewId() + "b"
|
|
|
|
_, err = ss.Post().Save(rctx, &o2)
|
|
require.NoError(t, err)
|
|
thread, errT := ss.Thread().Get(root.Id)
|
|
require.NoError(t, errT)
|
|
|
|
assert.Equal(t, int64(1), thread.ReplyCount)
|
|
assert.Equal(t, int(1), len(thread.Participants))
|
|
assert.Equal(t, model.StringArray{o2.UserId}, thread.Participants)
|
|
|
|
_, err = ss.Post().Save(rctx, &o3)
|
|
require.NoError(t, err)
|
|
thread, errT = ss.Thread().Get(root.Id)
|
|
require.NoError(t, errT)
|
|
|
|
assert.Equal(t, int64(2), thread.ReplyCount)
|
|
assert.Equal(t, int(2), len(thread.Participants))
|
|
assert.Equal(t, model.StringArray{o2.UserId, o3.UserId}, thread.Participants)
|
|
|
|
_, err = ss.Post().Save(rctx, &o4)
|
|
require.NoError(t, err)
|
|
thread, errT = ss.Thread().Get(root.Id)
|
|
require.NoError(t, errT)
|
|
|
|
assert.Equal(t, int64(3), thread.ReplyCount)
|
|
assert.Equal(t, int(2), len(thread.Participants))
|
|
assert.Equal(t, model.StringArray{o3.UserId, o2.UserId}, thread.Participants)
|
|
|
|
_, err = ss.Post().Save(rctx, &o5)
|
|
require.NoError(t, err)
|
|
thread, errT = ss.Thread().Get(root.Id)
|
|
require.NoError(t, errT)
|
|
|
|
assert.Equal(t, int64(4), thread.ReplyCount)
|
|
assert.Equal(t, int(3), len(thread.Participants))
|
|
assert.Equal(t, model.StringArray{o3.UserId, o2.UserId, o1.UserId}, thread.Participants)
|
|
})
|
|
}
|
|
|
|
func testPostStoreSaveChannelMsgCounts(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
c1 := &model.Channel{Name: model.NewId(), DisplayName: "posttestchannel", Type: model.ChannelTypeOpen, TeamId: model.NewId()}
|
|
_, err := ss.Channel().Save(rctx, c1, 1000000)
|
|
require.NoError(t, err)
|
|
|
|
o1 := model.Post{}
|
|
o1.ChannelId = c1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
|
|
_, err = ss.Post().Save(rctx, &o1)
|
|
require.NoError(t, err)
|
|
|
|
c1, err = ss.Channel().Get(c1.Id, false)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), c1.TotalMsgCount, "Message count should update by 1")
|
|
|
|
o1.Id = ""
|
|
o1.Type = model.PostTypeAddToTeam
|
|
_, err = ss.Post().Save(rctx, &o1)
|
|
require.NoError(t, err)
|
|
|
|
o1.Id = ""
|
|
o1.Type = model.PostTypeRemoveFromTeam
|
|
_, err = ss.Post().Save(rctx, &o1)
|
|
require.NoError(t, err)
|
|
|
|
c1, err = ss.Channel().Get(c1.Id, false)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), c1.TotalMsgCount, "Message count should not update for team add/removed message")
|
|
|
|
oldLastPostAt := c1.LastPostAt
|
|
|
|
o2 := model.Post{}
|
|
o2.ChannelId = c1.Id
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2.CreateAt = int64(7)
|
|
_, err = ss.Post().Save(rctx, &o2)
|
|
require.NoError(t, err)
|
|
|
|
c1, err = ss.Channel().Get(c1.Id, false)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, oldLastPostAt, c1.LastPostAt, "LastPostAt should not update for old message save")
|
|
}
|
|
|
|
func testPostStoreGet(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channel.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
|
|
etag1 := ss.Post().GetEtag(o1.ChannelId, false, false, false)
|
|
require.Equal(t, 0, strings.Index(etag1, model.CurrentVersion+"."), "Invalid Etag")
|
|
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
etag2 := ss.Post().GetEtag(o1.ChannelId, false, false, false)
|
|
require.Equal(t, 0, strings.Index(etag2, fmt.Sprintf("%v.%v", model.CurrentVersion, o1.UpdateAt)), "Invalid Etag")
|
|
|
|
r1, err := ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, r1.Posts[o1.Id].CreateAt, o1.CreateAt, "invalid returned post")
|
|
|
|
_, err = ss.Post().Get(rctx, "123", model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Missing id should have failed")
|
|
|
|
_, err = ss.Post().Get(rctx, "", model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "should fail for blank post ids")
|
|
}
|
|
|
|
func testPostStoreGetForThread(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t.Run("Post thread is followed", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{ChannelId: channel.Id, UserId: model.NewId(), Message: NewTestID()}
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
_, err = ss.Post().Save(rctx, &model.Post{ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestID(), RootId: o1.Id})
|
|
require.NoError(t, err)
|
|
|
|
_, err = ss.Thread().MaintainMembership(o1.UserId, o1.Id, store.ThreadMembershipOpts{
|
|
Following: true,
|
|
UpdateFollowing: true,
|
|
})
|
|
require.NoError(t, err)
|
|
opts := model.GetPostsOptions{
|
|
CollapsedThreads: true,
|
|
}
|
|
r1, err := ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, r1.Posts[o1.Id].CreateAt, o1.CreateAt, "invalid returned post")
|
|
require.True(t, *r1.Posts[o1.Id].IsFollowing)
|
|
})
|
|
|
|
t.Run("Post thread is explicitly not followed", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{ChannelId: channel.Id, UserId: model.NewId(), Message: NewTestID()}
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
_, err = ss.Post().Save(rctx, &model.Post{ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestID(), RootId: o1.Id})
|
|
require.NoError(t, err)
|
|
|
|
_, err = ss.Thread().MaintainMembership(o1.UserId, o1.Id, store.ThreadMembershipOpts{
|
|
Following: false,
|
|
UpdateFollowing: true,
|
|
})
|
|
require.NoError(t, err)
|
|
opts := model.GetPostsOptions{
|
|
CollapsedThreads: true,
|
|
}
|
|
r1, err := ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, r1.Posts[o1.Id].CreateAt, o1.CreateAt, "invalid returned post")
|
|
require.False(t, *r1.Posts[o1.Id].IsFollowing)
|
|
})
|
|
|
|
t.Run("Post threadmembership does not exist", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{ChannelId: channel.Id, UserId: model.NewId(), Message: NewTestID()}
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
_, err = ss.Post().Save(rctx, &model.Post{ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestID(), RootId: o1.Id})
|
|
require.NoError(t, err)
|
|
|
|
opts := model.GetPostsOptions{
|
|
CollapsedThreads: true,
|
|
}
|
|
r1, err := ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, r1.Posts[o1.Id].CreateAt, o1.CreateAt, "invalid returned post")
|
|
require.Nil(t, r1.Posts[o1.Id].IsFollowing)
|
|
})
|
|
|
|
t.Run("Pagination", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
now := model.GetMillis()
|
|
o1, err := ss.Post().Save(rctx, &model.Post{CreateAt: now, ChannelId: channel.Id, UserId: model.NewId(), Message: NewTestID()})
|
|
require.NoError(t, err)
|
|
_, err = ss.Post().Save(rctx, &model.Post{CreateAt: now + 1, ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestID(), RootId: o1.Id})
|
|
require.NoError(t, err)
|
|
m1, err := ss.Post().Save(rctx, &model.Post{CreateAt: now + 2, ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestID(), RootId: o1.Id})
|
|
require.NoError(t, err)
|
|
_, err = ss.Post().Save(rctx, &model.Post{CreateAt: now + 3, ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestID(), RootId: o1.Id})
|
|
require.NoError(t, err)
|
|
_, err = ss.Post().Save(rctx, &model.Post{CreateAt: now + 4, ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestID(), RootId: o1.Id})
|
|
require.NoError(t, err)
|
|
|
|
opts := model.GetPostsOptions{
|
|
CollapsedThreads: true,
|
|
PerPage: 2,
|
|
Direction: "down",
|
|
}
|
|
r1, err := ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Len(t, r1.Order, 3) // including the root post
|
|
require.Len(t, r1.Posts, 3)
|
|
assert.True(t, *r1.HasNext)
|
|
|
|
lastPostID := r1.Order[len(r1.Order)-1]
|
|
lastPostCreateAt := r1.Posts[lastPostID].CreateAt
|
|
|
|
opts = model.GetPostsOptions{
|
|
CollapsedThreads: true,
|
|
PerPage: 2,
|
|
Direction: "down",
|
|
FromPost: lastPostID,
|
|
FromCreateAt: lastPostCreateAt,
|
|
}
|
|
r1, err = ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Len(t, r1.Order, 3) // including the root post
|
|
require.Len(t, r1.Posts, 3)
|
|
assert.GreaterOrEqual(t, r1.Posts[r1.Order[len(r1.Order)-1]].CreateAt, lastPostCreateAt)
|
|
assert.False(t, *r1.HasNext)
|
|
|
|
// Going from bottom to top now.
|
|
firstPostCreateAt := r1.Posts[r1.Order[1]].CreateAt
|
|
opts = model.GetPostsOptions{
|
|
CollapsedThreads: true,
|
|
PerPage: 2,
|
|
Direction: "up",
|
|
FromPost: r1.Order[1],
|
|
FromCreateAt: firstPostCreateAt,
|
|
}
|
|
r1, err = ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Len(t, r1.Order, 3) // including the root post
|
|
require.Len(t, r1.Posts, 3)
|
|
assert.LessOrEqual(t, r1.Posts[r1.Order[1]].CreateAt, firstPostCreateAt)
|
|
assert.False(t, *r1.HasNext)
|
|
|
|
// Only with CreateAt
|
|
opts = model.GetPostsOptions{
|
|
CollapsedThreads: false,
|
|
PerPage: 1,
|
|
Direction: "up",
|
|
FromCreateAt: m1.CreateAt,
|
|
SkipFetchThreads: false,
|
|
}
|
|
r1, err = ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, r1.Posts[r1.Order[0]].ReplyCount, int64(4))
|
|
require.Equal(t, r1.Posts[r1.Order[1]].ReplyCount, int64(4))
|
|
require.Len(t, r1.Order, 2) // including the root post
|
|
require.Len(t, r1.Posts, 2)
|
|
assert.LessOrEqual(t, r1.Posts[r1.Order[1]].CreateAt, m1.CreateAt)
|
|
assert.True(t, *r1.HasNext)
|
|
|
|
// Non-CRT mode
|
|
opts = model.GetPostsOptions{
|
|
CollapsedThreads: false,
|
|
PerPage: 2,
|
|
Direction: "down",
|
|
SkipFetchThreads: false,
|
|
}
|
|
r1, err = ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Len(t, r1.Order, 2)
|
|
require.Len(t, r1.Posts, 2)
|
|
assert.True(t, *r1.HasNext)
|
|
|
|
lastPostID = r1.Order[len(r1.Order)-1]
|
|
lastPostCreateAt = r1.Posts[lastPostID].CreateAt
|
|
|
|
opts = model.GetPostsOptions{
|
|
CollapsedThreads: false,
|
|
PerPage: 3,
|
|
Direction: "down",
|
|
FromPost: lastPostID,
|
|
FromCreateAt: lastPostCreateAt,
|
|
SkipFetchThreads: false,
|
|
}
|
|
r1, err = ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, r1.Posts[r1.Order[0]].ReplyCount, int64(4))
|
|
require.Equal(t, r1.Posts[r1.Order[1]].ReplyCount, int64(4))
|
|
require.Equal(t, r1.Posts[r1.Order[2]].ReplyCount, int64(4))
|
|
require.Equal(t, r1.Posts[r1.Order[3]].ReplyCount, int64(4))
|
|
require.Len(t, r1.Order, 4) // including the root post
|
|
require.Len(t, r1.Posts, 4)
|
|
assert.GreaterOrEqual(t, r1.Posts[r1.Order[len(r1.Order)-1]].CreateAt, lastPostCreateAt)
|
|
assert.False(t, *r1.HasNext)
|
|
|
|
// Going from bottom to top now.
|
|
firstPostCreateAt = r1.Posts[r1.Order[1]].CreateAt
|
|
opts = model.GetPostsOptions{
|
|
CollapsedThreads: false,
|
|
PerPage: 2,
|
|
Direction: "up",
|
|
FromPost: r1.Order[1],
|
|
FromCreateAt: firstPostCreateAt,
|
|
SkipFetchThreads: false,
|
|
}
|
|
r1, err = ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Len(t, r1.Order, 2)
|
|
require.Len(t, r1.Posts, 2)
|
|
assert.LessOrEqual(t, r1.Posts[r1.Order[1]].CreateAt, firstPostCreateAt)
|
|
assert.False(t, *r1.HasNext)
|
|
|
|
// Only with CreateAt
|
|
opts = model.GetPostsOptions{
|
|
CollapsedThreads: false,
|
|
PerPage: 1,
|
|
Direction: "down",
|
|
FromCreateAt: m1.CreateAt,
|
|
SkipFetchThreads: false,
|
|
}
|
|
r1, err = ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Len(t, r1.Order, 2) // including the root post
|
|
require.Len(t, r1.Posts, 2)
|
|
assert.GreaterOrEqual(t, r1.Posts[r1.Order[1]].CreateAt, m1.CreateAt)
|
|
assert.True(t, *r1.HasNext)
|
|
})
|
|
|
|
t.Run("Pagination with UpdateAt", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
now := model.GetMillis()
|
|
o1 := &model.Post{CreateAt: now, ChannelId: channel.Id, UserId: model.NewId(), Message: NewTestID()}
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
// Create replies with explicit UpdateAt timestamps
|
|
o2 := &model.Post{CreateAt: now + 1, UpdateAt: now + 1, ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestID(), RootId: o1.Id}
|
|
_, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
m1 := &model.Post{CreateAt: now + 2, UpdateAt: now + 2, ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestID(), RootId: o1.Id}
|
|
m1, err = ss.Post().Save(rctx, m1)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{CreateAt: now + 3, UpdateAt: now + 3, ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestID(), RootId: o1.Id}
|
|
_, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
o4 := &model.Post{CreateAt: now + 4, UpdateAt: now + 4, ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestID(), RootId: o1.Id}
|
|
o4, err = ss.Post().Save(rctx, o4)
|
|
require.NoError(t, err)
|
|
|
|
// Test pagination with UpdateAt in "down" direction
|
|
opts := model.GetPostsOptions{
|
|
UpdatesOnly: true,
|
|
CollapsedThreads: true,
|
|
PerPage: 2,
|
|
Direction: "down",
|
|
}
|
|
r1, err := ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Len(t, r1.Order, 3) // including the root post
|
|
require.Len(t, r1.Posts, 3)
|
|
assert.Equal(t, r1.Posts[r1.Order[0]].UpdateAt, o4.CreateAt) // The root post always get updated with the createAt of the latest post in the thread.
|
|
assert.True(t, *r1.HasNext)
|
|
|
|
lastPostID := r1.Order[len(r1.Order)-1]
|
|
lastPostUpdateAt := r1.Posts[lastPostID].UpdateAt
|
|
|
|
// Continue pagination using UpdateAt
|
|
opts = model.GetPostsOptions{
|
|
UpdatesOnly: true,
|
|
CollapsedThreads: true,
|
|
PerPage: 2,
|
|
Direction: "down",
|
|
FromPost: lastPostID,
|
|
FromUpdateAt: lastPostUpdateAt,
|
|
}
|
|
r1, err = ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Len(t, r1.Order, 3) // including the root post
|
|
require.Len(t, r1.Posts, 3)
|
|
assert.GreaterOrEqual(t, r1.Posts[r1.Order[len(r1.Order)-1]].UpdateAt, lastPostUpdateAt)
|
|
assert.Equal(t, r1.Posts[r1.Order[0]].UpdateAt, o4.CreateAt) // The root post always get updated with the createAt of the latest post in the thread.
|
|
assert.False(t, *r1.HasNext)
|
|
|
|
// Non-CRT mode with UpdateAt pagination
|
|
opts = model.GetPostsOptions{
|
|
UpdatesOnly: true,
|
|
CollapsedThreads: false,
|
|
PerPage: 2,
|
|
Direction: "down",
|
|
SkipFetchThreads: false,
|
|
}
|
|
r1, err = ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
// Ordering by updateAt will move the root post down, so we will get more posts in the thread.
|
|
require.Len(t, r1.Order, 3)
|
|
require.Len(t, r1.Posts, 3)
|
|
require.True(t, *r1.HasNext)
|
|
|
|
lastPostID = r1.Order[len(r1.Order)-1]
|
|
lastPostUpdateAt = r1.Posts[lastPostID].UpdateAt
|
|
|
|
opts = model.GetPostsOptions{
|
|
UpdatesOnly: true,
|
|
CollapsedThreads: false,
|
|
PerPage: 3,
|
|
Direction: "down",
|
|
FromPost: lastPostID,
|
|
FromUpdateAt: lastPostUpdateAt,
|
|
SkipFetchThreads: false,
|
|
}
|
|
r1, err = ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Len(t, r1.Order, 3)
|
|
require.Len(t, r1.Posts, 3)
|
|
require.Equal(t, r1.Posts[r1.Order[0]].ReplyCount, int64(4))
|
|
require.Equal(t, r1.Posts[r1.Order[1]].ReplyCount, int64(4))
|
|
require.Equal(t, r1.Posts[r1.Order[2]].ReplyCount, int64(4))
|
|
require.GreaterOrEqual(t, r1.Posts[r1.Order[len(r1.Order)-1]].UpdateAt, lastPostUpdateAt)
|
|
assert.False(t, *r1.HasNext)
|
|
|
|
// Only with UpdateAt - direction down
|
|
opts = model.GetPostsOptions{
|
|
UpdatesOnly: true,
|
|
CollapsedThreads: false,
|
|
PerPage: 1,
|
|
Direction: "down",
|
|
FromUpdateAt: m1.UpdateAt,
|
|
SkipFetchThreads: false,
|
|
}
|
|
r1, err = ss.Post().Get(rctx, o1.Id, opts, o1.UserId, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Len(t, r1.Order, 2)
|
|
require.Len(t, r1.Posts, 2)
|
|
require.GreaterOrEqual(t, r1.Posts[r1.Order[1]].UpdateAt, m1.UpdateAt)
|
|
require.True(t, *r1.HasNext)
|
|
})
|
|
}
|
|
|
|
func testPostStoreGetSingle(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channel.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = o1.UserId
|
|
o2.Message = NewTestID()
|
|
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = o1.ChannelId
|
|
o3.UserId = o1.UserId
|
|
o3.Message = model.NewRandomString(10)
|
|
o3.RootId = o1.Id
|
|
|
|
o4 := &model.Post{}
|
|
o4.ChannelId = o1.ChannelId
|
|
o4.UserId = o1.UserId
|
|
o4.Message = model.NewRandomString(10)
|
|
o4.RootId = o1.Id
|
|
|
|
_, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
o4, err = ss.Post().Save(rctx, o4)
|
|
require.NoError(t, err)
|
|
|
|
err = ss.Post().Delete(rctx, o2.Id, model.GetMillis(), o2.UserId)
|
|
require.NoError(t, err)
|
|
|
|
err = ss.Post().Delete(rctx, o4.Id, model.GetMillis(), o4.UserId)
|
|
require.NoError(t, err)
|
|
|
|
post, err := ss.Post().GetSingle(rctx, o1.Id, false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, post.CreateAt, o1.CreateAt, "invalid returned post")
|
|
require.Equal(t, int64(1), post.ReplyCount, "wrong replyCount computed")
|
|
|
|
_, err = ss.Post().GetSingle(rctx, o2.Id, false)
|
|
require.Error(t, err, "should not return deleted post")
|
|
|
|
post, err = ss.Post().GetSingle(rctx, o2.Id, true)
|
|
require.NoError(t, err)
|
|
require.Equal(t, post.CreateAt, o2.CreateAt, "invalid returned post")
|
|
require.NotZero(t, post.DeleteAt, "DeleteAt should be non-zero")
|
|
require.Zero(t, post.ReplyCount, "Post without replies should return zero ReplyCount")
|
|
|
|
_, err = ss.Post().GetSingle(rctx, "123", false)
|
|
require.Error(t, err, "Missing id should have failed")
|
|
}
|
|
|
|
func testPostStoreUpdate(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channel.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2.RootId = o1.Id
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = o1.ChannelId
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
r1, err := ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro1 := r1.Posts[o1.Id]
|
|
|
|
r2, err := ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro2 := r2.Posts[o2.Id]
|
|
|
|
r3, err := ss.Post().Get(rctx, o3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro3 := r3.Posts[o3.Id]
|
|
|
|
require.Equal(t, ro1.Message, o1.Message, "Failed to save/get")
|
|
|
|
o1a := ro1.Clone()
|
|
o1a.Message = ro1.Message + "BBBBBBBBBB"
|
|
_, err = ss.Post().Update(rctx, o1a, ro1)
|
|
require.NoError(t, err)
|
|
|
|
r1, err = ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
ro1a := r1.Posts[o1.Id]
|
|
require.Equal(t, ro1a.Message, o1a.Message, "Failed to update/get")
|
|
|
|
o2a := ro2.Clone()
|
|
o2a.Message = ro2.Message + "DDDDDDD"
|
|
_, err = ss.Post().Update(rctx, o2a, ro2)
|
|
require.NoError(t, err)
|
|
|
|
r2, err = ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro2a := r2.Posts[o2.Id]
|
|
|
|
require.Equal(t, ro2a.Message, o2a.Message, "Failed to update/get")
|
|
|
|
o3a := ro3.Clone()
|
|
o3a.Message = ro3.Message + "WWWWWWW"
|
|
_, err = ss.Post().Update(rctx, o3a, ro3)
|
|
require.NoError(t, err)
|
|
|
|
r3, err = ss.Post().Get(rctx, o3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro3a := r3.Posts[o3.Id]
|
|
|
|
if ro3a.Message != o3a.Message {
|
|
require.Equal(t, ro3a.Hashtags, o3a.Hashtags, "Failed to update/get")
|
|
}
|
|
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
o4, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel2.Id,
|
|
UserId: model.NewId(),
|
|
Message: model.NewId(),
|
|
Filenames: []string{"test"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
r4, err := ss.Post().Get(rctx, o4.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro4 := r4.Posts[o4.Id]
|
|
|
|
o4a := ro4.Clone()
|
|
o4a.Filenames = []string{}
|
|
o4a.FileIds = []string{model.NewId()}
|
|
_, err = ss.Post().Update(rctx, o4a, ro4)
|
|
require.NoError(t, err)
|
|
|
|
r4, err = ss.Post().Get(rctx, o4.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
ro4a := r4.Posts[o4.Id]
|
|
require.Empty(t, ro4a.Filenames, "Failed to clear Filenames")
|
|
require.Len(t, ro4a.FileIds, 1, "Failed to set FileIds")
|
|
}
|
|
|
|
func testPostStoreDelete(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t.Run("single post, no replies", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
// Create a post
|
|
rootPost, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel.Id,
|
|
UserId: model.NewId(),
|
|
Message: model.NewRandomString(10),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify etag generation for the channel containing the post.
|
|
etag1 := ss.Post().GetEtag(rootPost.ChannelId, false, false, false)
|
|
require.Equal(t, 0, strings.Index(etag1, model.CurrentVersion+"."), "Invalid Etag")
|
|
|
|
// Verify the created post.
|
|
r1, err := ss.Post().Get(rctx, rootPost.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, r1.Posts[rootPost.Id])
|
|
require.Equal(t, rootPost, r1.Posts[rootPost.Id])
|
|
|
|
// Mark the post as deleted by the user identified with deleteByID.
|
|
deleteByID := model.NewId()
|
|
err = ss.Post().Delete(rctx, rootPost.Id, model.GetMillis(), deleteByID)
|
|
require.NoError(t, err)
|
|
|
|
// Ensure the appropriate posts prop reflects the user deleting the post.
|
|
posts, err := ss.Post().GetPostsCreatedAt(rootPost.ChannelId, rootPost.CreateAt)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, posts)
|
|
assert.Equal(t, deleteByID, posts[0].GetProp(model.PostPropsDeleteBy), "unexpected Props[model.PostPropsDeleteBy]")
|
|
|
|
// Verify that the post is no longer fetched by default.
|
|
_, err = ss.Post().Get(rctx, rootPost.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "fetching deleted post should have failed")
|
|
require.IsType(t, &store.ErrNotFound{}, err)
|
|
|
|
// Verify etag generation for the channel containing the now deleted post.
|
|
etag2 := ss.Post().GetEtag(rootPost.ChannelId, false, false, false)
|
|
require.Equal(t, 0, strings.Index(etag2, model.CurrentVersion+"."), "Invalid Etag")
|
|
})
|
|
|
|
t.Run("thread with one reply", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
// Create a root post
|
|
rootPost, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel.Id,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Reply to that root post
|
|
replyPost, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: rootPost.ChannelId,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
RootId: rootPost.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Delete the root post
|
|
err = ss.Post().Delete(rctx, rootPost.Id, model.GetMillis(), "")
|
|
require.NoError(t, err)
|
|
|
|
// Verify the root post deleted
|
|
_, err = ss.Post().Get(rctx, rootPost.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
require.IsType(t, &store.ErrNotFound{}, err)
|
|
|
|
// Verify the reply post deleted
|
|
_, err = ss.Post().Get(rctx, replyPost.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
require.IsType(t, &store.ErrNotFound{}, err)
|
|
})
|
|
|
|
t.Run("thread with multiple replies", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
// Create a root post
|
|
rootPost1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel.Id,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Reply to that root post
|
|
replyPost1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: rootPost1.ChannelId,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
RootId: rootPost1.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Reply to that root post a second time
|
|
replyPost2, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: rootPost1.ChannelId,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
RootId: rootPost1.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
// Create another root post in a separate channel
|
|
rootPost2, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel2.Id,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Delete the root post
|
|
err = ss.Post().Delete(rctx, rootPost1.Id, model.GetMillis(), "")
|
|
require.NoError(t, err)
|
|
|
|
// Verify the root post and replies deleted
|
|
_, err = ss.Post().Get(rctx, rootPost1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
|
|
_, err = ss.Post().Get(rctx, replyPost1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
|
|
_, err = ss.Post().Get(rctx, replyPost2.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
|
|
// Verify other root posts remain undeleted.
|
|
_, err = ss.Post().Get(rctx, rootPost2.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("root post update at is updated upon reply delete", func(t *testing.T) {
|
|
teamId := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamId,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
// Create a root post
|
|
rootPost1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel.Id,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Reply to that root post
|
|
_, err = ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: rootPost1.ChannelId,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
RootId: rootPost1.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Reply to that root post a second time
|
|
replyPost2, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: rootPost1.ChannelId,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
RootId: rootPost1.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Reply to that root post a third time
|
|
_, err = ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: rootPost1.ChannelId,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
RootId: rootPost1.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
updatedRootPost, err := ss.Post().GetSingle(rctx, rootPost1.Id, false)
|
|
require.NoError(t, err)
|
|
|
|
beforeDeleteTime := updatedRootPost.UpdateAt
|
|
|
|
// Delete the reply previous to last
|
|
err = ss.Post().Delete(rctx, replyPost2.Id, model.GetMillis(), "")
|
|
require.NoError(t, err)
|
|
|
|
updatedRootPost, err = ss.Post().GetSingle(rctx, rootPost1.Id, false)
|
|
require.NoError(t, err)
|
|
|
|
require.Greater(t, updatedRootPost.UpdateAt, beforeDeleteTime)
|
|
})
|
|
|
|
t.Run("thread with multiple replies, update thread last reply at", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
// Create a root post
|
|
rootPost1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel.Id,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Reply to that root post
|
|
replyPost1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: rootPost1.ChannelId,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
RootId: rootPost1.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Reply to that root post a second time
|
|
replyPost2, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: rootPost1.ChannelId,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
RootId: rootPost1.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Reply to that root post a third time
|
|
replyPost3, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: rootPost1.ChannelId,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
RootId: rootPost1.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
thread, err := ss.Thread().Get(rootPost1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, replyPost3.CreateAt, thread.LastReplyAt)
|
|
|
|
// Delete the reply previous to last
|
|
err = ss.Post().Delete(rctx, replyPost2.Id, model.GetMillis(), "")
|
|
require.NoError(t, err)
|
|
|
|
thread, err = ss.Thread().Get(rootPost1.Id)
|
|
require.NoError(t, err)
|
|
// last reply at should be unchanged
|
|
require.Equal(t, replyPost3.CreateAt, thread.LastReplyAt)
|
|
|
|
// Delete the last reply
|
|
err = ss.Post().Delete(rctx, replyPost3.Id, model.GetMillis(), "")
|
|
require.NoError(t, err)
|
|
|
|
thread, err = ss.Thread().Get(rootPost1.Id)
|
|
require.NoError(t, err)
|
|
// last reply at should have changed
|
|
require.Equal(t, replyPost1.CreateAt, thread.LastReplyAt)
|
|
|
|
// Delete the last reply
|
|
err = ss.Post().Delete(rctx, replyPost1.Id, model.GetMillis(), "")
|
|
require.NoError(t, err)
|
|
|
|
thread, err = ss.Thread().Get(rootPost1.Id)
|
|
require.NoError(t, err)
|
|
// last reply at should be 0
|
|
require.Equal(t, int64(0), thread.LastReplyAt)
|
|
})
|
|
|
|
t.Run("thread with file attachments", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
// Create a root post
|
|
rootPost1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel.Id,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Create another root post
|
|
rootPost2, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel.Id,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Reply to first root post with file attachments
|
|
replyPost1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: rootPost1.ChannelId,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
RootId: rootPost1.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
file11, err := ss.FileInfo().Save(rctx, &model.FileInfo{
|
|
Id: model.NewId(),
|
|
PostId: replyPost1.Id,
|
|
CreatorId: replyPost1.UserId,
|
|
Path: "file1.txt",
|
|
})
|
|
require.NoError(t, err)
|
|
file12, err := ss.FileInfo().Save(rctx, &model.FileInfo{
|
|
Id: model.NewId(),
|
|
PostId: replyPost1.Id,
|
|
CreatorId: replyPost1.UserId,
|
|
Path: "file2.png",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Reply to second root post with file attachments
|
|
replyPost2, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: rootPost2.ChannelId,
|
|
UserId: model.NewId(),
|
|
Message: NewTestID(),
|
|
RootId: rootPost2.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
file21, err := ss.FileInfo().Save(rctx, &model.FileInfo{
|
|
Id: model.NewId(),
|
|
PostId: replyPost2.Id,
|
|
CreatorId: replyPost2.UserId,
|
|
Path: "file1.txt",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Delete the first root post
|
|
err = ss.Post().Delete(rctx, rootPost1.Id, model.GetMillis(), "")
|
|
require.NoError(t, err)
|
|
|
|
// Verify the reply post's files are deleted
|
|
_, err = ss.FileInfo().Get(file11.Id)
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
require.IsType(t, &store.ErrNotFound{}, err)
|
|
_, err = ss.FileInfo().Get(file12.Id)
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
require.IsType(t, &store.ErrNotFound{}, err)
|
|
|
|
// Verify the other reply post's files are NOT deleted
|
|
_, err = ss.FileInfo().Get(file21.Id)
|
|
require.NoError(t, err, "Not deleted id should have succeeded")
|
|
})
|
|
}
|
|
|
|
func testPostStorePermDelete1Level(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channel.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2.RootId = o1.Id
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
r1 := &model.Reaction{}
|
|
r1.ChannelId = o1.ChannelId
|
|
r1.UserId = o2.UserId
|
|
r1.PostId = o1.Id
|
|
r1.EmojiName = "smile"
|
|
r1, err = ss.Reaction().Save(r1)
|
|
require.NoError(t, err)
|
|
|
|
r2 := &model.Reaction{}
|
|
r2.ChannelId = o1.ChannelId
|
|
r2.UserId = o1.UserId
|
|
r2.PostId = o2.Id
|
|
r2.EmojiName = "wave"
|
|
_, err = ss.Reaction().Save(r2)
|
|
require.NoError(t, err)
|
|
|
|
r3 := &model.Reaction{}
|
|
r3.ChannelId = o1.ChannelId
|
|
r3.UserId = model.NewId()
|
|
r3.PostId = o1.Id
|
|
r3.EmojiName = "sad"
|
|
r3, err = ss.Reaction().Save(r3)
|
|
require.NoError(t, err)
|
|
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName2",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = channel2.Id
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
r4 := &model.Reaction{}
|
|
r4.ChannelId = channel2.Id
|
|
r4.UserId = model.NewId()
|
|
r4.PostId = o3.Id
|
|
r4.EmojiName = "angry"
|
|
_, err = ss.Reaction().Save(r4)
|
|
require.NoError(t, err)
|
|
|
|
channel3, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName3",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o4 := &model.Post{}
|
|
o4.ChannelId = channel3.Id
|
|
o4.RootId = o1.Id
|
|
o4.UserId = o2.UserId
|
|
o4.Message = NewTestID()
|
|
o4, err = ss.Post().Save(rctx, o4)
|
|
require.NoError(t, err)
|
|
|
|
o5 := &model.Post{}
|
|
o5.ChannelId = o3.ChannelId
|
|
o5.UserId = model.NewId()
|
|
o5.Message = NewTestID()
|
|
o5, err = ss.Post().Save(rctx, o5)
|
|
require.NoError(t, err)
|
|
|
|
o6 := &model.Post{}
|
|
o6.ChannelId = o3.ChannelId
|
|
o6.RootId = o5.Id
|
|
o6.UserId = model.NewId()
|
|
o6.Message = NewTestID()
|
|
o6, err = ss.Post().Save(rctx, o6)
|
|
require.NoError(t, err)
|
|
|
|
var thread *model.Thread
|
|
thread, err = ss.Thread().Get(o1.Id)
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, 2, thread.ReplyCount)
|
|
require.EqualValues(t, model.StringArray{o2.UserId}, thread.Participants)
|
|
|
|
err2 := ss.Post().PermanentDeleteByUser(rctx, o2.UserId)
|
|
require.NoError(t, err2)
|
|
|
|
thread, err = ss.Thread().Get(o1.Id)
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, 0, thread.ReplyCount)
|
|
require.EqualValues(t, model.StringArray{}, thread.Participants)
|
|
|
|
_, err = ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err, "Deleted id shouldn't have failed")
|
|
|
|
reactions, err := ss.Reaction().GetForPost(o1.Id, false)
|
|
require.NoError(t, err, "Reactions should exist")
|
|
require.Equal(t, 2, len(reactions))
|
|
emojis := []string{r1.EmojiName, r3.EmojiName}
|
|
for _, reaction := range reactions {
|
|
require.Contains(t, emojis, reaction.EmojiName)
|
|
}
|
|
|
|
_, err = ss.Post().Get(rctx, o2.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
|
|
reactions, err = ss.Reaction().GetForPost(o2.Id, false)
|
|
require.NoError(t, err, "No error for not found")
|
|
require.Equal(t, 0, len(reactions))
|
|
|
|
thread, err = ss.Thread().Get(o5.Id)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, thread)
|
|
|
|
err = ss.Post().PermanentDeleteByChannel(rctx, o3.ChannelId)
|
|
require.NoError(t, err)
|
|
|
|
thread, err = ss.Thread().Get(o5.Id)
|
|
require.NoError(t, err)
|
|
require.Nil(t, thread)
|
|
|
|
reactions, err = ss.Reaction().GetForPost(o3.Id, false)
|
|
require.NoError(t, err, "No error for not found")
|
|
require.Equal(t, 0, len(reactions))
|
|
|
|
reactions, err = ss.Reaction().GetForPost(o1.Id, false)
|
|
require.NoError(t, err, "Reactions should exist")
|
|
require.Equal(t, 2, len(reactions))
|
|
for _, reaction := range reactions {
|
|
require.Contains(t, emojis, reaction.EmojiName)
|
|
}
|
|
|
|
_, err = ss.Post().Get(rctx, o3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
|
|
_, err = ss.Post().Get(rctx, o4.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
|
|
_, err = ss.Post().Get(rctx, o5.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
|
|
_, err = ss.Post().Get(rctx, o6.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
}
|
|
|
|
func testPostStorePermDelete1Level2(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channel1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2.RootId = o1.Id
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName2",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = channel2.Id
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
err2 := ss.Post().PermanentDeleteByUser(rctx, o1.UserId)
|
|
require.NoError(t, err2)
|
|
|
|
_, err = ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
|
|
_, err = ss.Post().Get(rctx, o2.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Deleted id should have failed")
|
|
|
|
_, err = ss.Post().Get(rctx, o3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err, "Deleted id should have failed")
|
|
}
|
|
|
|
func testPostStorePermDeleteLimitExceeded(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
const maxPosts = 10000
|
|
teamID := model.NewId()
|
|
userID := model.NewId()
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "10KPosts",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
for range maxPosts + 100 {
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
UserId: userID,
|
|
Message: NewTestID(),
|
|
}
|
|
_, err = ss.Post().Save(rctx, post)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
err = ss.Post().PermanentDeleteByUser(rctx, userID)
|
|
var errLimitExceeded *store.ErrLimitExceeded
|
|
require.ErrorAs(t, err, &errLimitExceeded)
|
|
}
|
|
|
|
func testPostStoreGetWithChildren(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channel1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2.RootId = o1.Id
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = o1.ChannelId
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3.RootId = o1.Id
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
pl, err := ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, pl.Posts, 3, "invalid returned post")
|
|
|
|
dErr := ss.Post().Delete(rctx, o3.Id, model.GetMillis(), "")
|
|
require.NoError(t, dErr)
|
|
|
|
pl, err = ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, pl.Posts, 2, "invalid returned post")
|
|
|
|
dErr = ss.Post().Delete(rctx, o2.Id, model.GetMillis(), "")
|
|
require.NoError(t, dErr)
|
|
|
|
pl, err = ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, pl.Posts, 1, "invalid returned post")
|
|
}
|
|
|
|
func testPostStoreGetPostsWithDetails(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channel1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2.RootId = o1.Id
|
|
_, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o2a := &model.Post{}
|
|
o2a.ChannelId = o1.ChannelId
|
|
o2a.UserId = model.NewId()
|
|
o2a.Message = NewTestID()
|
|
o2a.RootId = o1.Id
|
|
o2a, err = ss.Post().Save(rctx, o2a)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = o1.ChannelId
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3.RootId = o1.Id
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o4 := &model.Post{}
|
|
o4.ChannelId = o1.ChannelId
|
|
o4.UserId = model.NewId()
|
|
o4.Message = NewTestID()
|
|
o4, err = ss.Post().Save(rctx, o4)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o5 := &model.Post{}
|
|
o5.ChannelId = o1.ChannelId
|
|
o5.UserId = model.NewId()
|
|
o5.Message = NewTestID()
|
|
o5.RootId = o4.Id
|
|
o5, err = ss.Post().Save(rctx, o5)
|
|
require.NoError(t, err)
|
|
|
|
r1, err := ss.Post().GetPosts(rctx, model.GetPostsOptions{ChannelId: o1.ChannelId, Page: 0, PerPage: 4}, false, map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, r1.Order[0], o5.Id, "invalid order")
|
|
require.Equal(t, r1.Order[1], o4.Id, "invalid order")
|
|
require.Equal(t, r1.Order[2], o3.Id, "invalid order")
|
|
require.Equal(t, r1.Order[3], o2a.Id, "invalid order")
|
|
|
|
//the last 4, + o1 (o2a and o3's parent) + o2 (in same thread as o2a and o3)
|
|
require.Len(t, r1.Posts, 6, "wrong size")
|
|
|
|
require.Equal(t, r1.Posts[o1.Id].Message, o1.Message, "Missing parent")
|
|
|
|
r2, err := ss.Post().GetPosts(rctx, model.GetPostsOptions{ChannelId: o1.ChannelId, Page: 0, PerPage: 4}, false, map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, r2.Order[0], o5.Id, "invalid order")
|
|
require.Equal(t, r2.Order[1], o4.Id, "invalid order")
|
|
require.Equal(t, r2.Order[2], o3.Id, "invalid order")
|
|
require.Equal(t, r2.Order[3], o2a.Id, "invalid order")
|
|
|
|
//the last 4, + o1 (o2a and o3's parent) + o2 (in same thread as o2a and o3)
|
|
require.Len(t, r2.Posts, 6, "wrong size")
|
|
|
|
require.Equal(t, r2.Posts[o1.Id].Message, o1.Message, "Missing parent")
|
|
|
|
// Run once to fill cache
|
|
_, err = ss.Post().GetPosts(rctx, model.GetPostsOptions{ChannelId: o1.ChannelId, Page: 0, PerPage: 30}, false, map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
o6 := &model.Post{}
|
|
o6.ChannelId = o1.ChannelId
|
|
o6.UserId = model.NewId()
|
|
o6.Message = NewTestID()
|
|
_, err = ss.Post().Save(rctx, o6)
|
|
require.NoError(t, err)
|
|
|
|
r3, err := ss.Post().GetPosts(rctx, model.GetPostsOptions{ChannelId: o1.ChannelId, Page: 0, PerPage: 30}, false, map[string]bool{})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 7, len(r3.Order))
|
|
}
|
|
|
|
func testPostStoreGetPostsBeforeAfter(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t.Run("without threads", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channelID := channel1.Id
|
|
userID := model.NewId()
|
|
|
|
var posts []*model.Post
|
|
for range 10 {
|
|
post, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
posts = append(posts, post)
|
|
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
|
|
t.Run("should return error if negative Page/PerPage options are passed", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsAfter(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: posts[0].Id, Page: 0, PerPage: -1}, map[string]bool{})
|
|
assert.Nil(t, postList)
|
|
assert.Error(t, err)
|
|
assert.IsType(t, &store.ErrInvalidInput{}, err)
|
|
|
|
postList, err = ss.Post().GetPostsAfter(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: posts[0].Id, Page: -1, PerPage: 10}, map[string]bool{})
|
|
assert.Nil(t, postList)
|
|
assert.Error(t, err)
|
|
assert.IsType(t, &store.ErrInvalidInput{}, err)
|
|
})
|
|
|
|
t.Run("should not return anything before the first post", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsBefore(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: posts[0].Id, Page: 0, PerPage: 10}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{}, postList.Order)
|
|
assert.Equal(t, map[string]*model.Post{}, postList.Posts)
|
|
})
|
|
|
|
t.Run("should return posts before a post", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsBefore(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: posts[5].Id, Page: 0, PerPage: 10}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{posts[4].Id, posts[3].Id, posts[2].Id, posts[1].Id, posts[0].Id}, postList.Order)
|
|
assert.Equal(t, map[string]*model.Post{
|
|
posts[0].Id: posts[0],
|
|
posts[1].Id: posts[1],
|
|
posts[2].Id: posts[2],
|
|
posts[3].Id: posts[3],
|
|
posts[4].Id: posts[4],
|
|
}, postList.Posts)
|
|
})
|
|
|
|
t.Run("should limit posts before", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsBefore(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: posts[5].Id, PerPage: 2}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{posts[4].Id, posts[3].Id}, postList.Order)
|
|
assert.Equal(t, map[string]*model.Post{
|
|
posts[3].Id: posts[3],
|
|
posts[4].Id: posts[4],
|
|
}, postList.Posts)
|
|
})
|
|
|
|
t.Run("should not return anything after the last post", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsAfter(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: posts[len(posts)-1].Id, PerPage: 10}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{}, postList.Order)
|
|
assert.Equal(t, map[string]*model.Post{}, postList.Posts)
|
|
})
|
|
|
|
t.Run("should return posts after a post", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsAfter(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: posts[5].Id, PerPage: 10}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{posts[9].Id, posts[8].Id, posts[7].Id, posts[6].Id}, postList.Order)
|
|
assert.Equal(t, map[string]*model.Post{
|
|
posts[6].Id: posts[6],
|
|
posts[7].Id: posts[7],
|
|
posts[8].Id: posts[8],
|
|
posts[9].Id: posts[9],
|
|
}, postList.Posts)
|
|
})
|
|
|
|
t.Run("should limit posts after", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsAfter(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: posts[5].Id, PerPage: 2}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{posts[7].Id, posts[6].Id}, postList.Order)
|
|
assert.Equal(t, map[string]*model.Post{
|
|
posts[6].Id: posts[6],
|
|
posts[7].Id: posts[7],
|
|
}, postList.Posts)
|
|
})
|
|
})
|
|
t.Run("with threads", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channelID := channel1.Id
|
|
userID := model.NewId()
|
|
|
|
// This creates a series of posts that looks like:
|
|
// post1
|
|
// post2
|
|
// post3 (in response to post1)
|
|
// post4 (in response to post2)
|
|
// post5
|
|
// post6 (in response to post2)
|
|
|
|
post1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
post1.ReplyCount = 1
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post2, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
post2.ReplyCount = 2
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post3, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
RootId: post1.Id,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
post3.ReplyCount = 1
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post4, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
RootId: post2.Id,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
post4.ReplyCount = 2
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post5, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post6, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
RootId: post2.Id,
|
|
Message: "message",
|
|
})
|
|
post6.ReplyCount = 2
|
|
require.NoError(t, err)
|
|
|
|
// Adding a post to a thread changes the UpdateAt timestamp of the parent post
|
|
post1.UpdateAt = post3.UpdateAt
|
|
post2.UpdateAt = post6.UpdateAt
|
|
|
|
t.Run("should return each post and thread before a post", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsBefore(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: post4.Id, PerPage: 2}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{post3.Id, post2.Id}, postList.Order)
|
|
assert.Equal(t, map[string]*model.Post{
|
|
post1.Id: post1,
|
|
post2.Id: post2,
|
|
post3.Id: post3,
|
|
post4.Id: post4,
|
|
post6.Id: post6,
|
|
}, postList.Posts)
|
|
})
|
|
|
|
t.Run("should return each post and the root of each thread after a post", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsAfter(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: post4.Id, PerPage: 2}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{post6.Id, post5.Id}, postList.Order)
|
|
assert.Equal(t, map[string]*model.Post{
|
|
post2.Id: post2,
|
|
post4.Id: post4,
|
|
post5.Id: post5,
|
|
post6.Id: post6,
|
|
}, postList.Posts)
|
|
})
|
|
})
|
|
t.Run("with threads (skipFetchThreads)", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channelID := channel1.Id
|
|
userID := model.NewId()
|
|
|
|
// This creates a series of posts that looks like:
|
|
// post1
|
|
// post2
|
|
// post3 (in response to post1)
|
|
// post4 (in response to post2)
|
|
// post5
|
|
// post6 (in response to post2)
|
|
|
|
post1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "post1",
|
|
})
|
|
require.NoError(t, err)
|
|
post1.ReplyCount = 1
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post2, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "post2",
|
|
})
|
|
require.NoError(t, err)
|
|
post2.ReplyCount = 2
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post3, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
RootId: post1.Id,
|
|
Message: "post3",
|
|
})
|
|
require.NoError(t, err)
|
|
post3.ReplyCount = 1
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post4, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
RootId: post2.Id,
|
|
Message: "post4",
|
|
})
|
|
require.NoError(t, err)
|
|
post4.ReplyCount = 2
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post5, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "post5",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post6, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
RootId: post2.Id,
|
|
Message: "post6",
|
|
})
|
|
post6.ReplyCount = 2
|
|
require.NoError(t, err)
|
|
|
|
// Adding a post to a thread changes the UpdateAt timestamp of the parent post
|
|
post1.UpdateAt = post3.UpdateAt
|
|
post2.UpdateAt = post6.UpdateAt
|
|
|
|
t.Run("should return each post and thread before a post", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsBefore(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: post4.Id, PerPage: 2, SkipFetchThreads: true}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{post3.Id, post2.Id}, postList.Order)
|
|
assert.Equal(t, map[string]*model.Post{
|
|
post1.Id: post1,
|
|
post2.Id: post2,
|
|
post3.Id: post3,
|
|
}, postList.Posts)
|
|
})
|
|
|
|
t.Run("should return each post and thread before a post with limit", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsBefore(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: post4.Id, PerPage: 1, SkipFetchThreads: true}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{post3.Id}, postList.Order)
|
|
assert.Equal(t, map[string]*model.Post{
|
|
post1.Id: post1,
|
|
post3.Id: post3,
|
|
}, postList.Posts)
|
|
})
|
|
|
|
t.Run("should return each post and the root of each thread after a post", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsAfter(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: post4.Id, PerPage: 2, SkipFetchThreads: true}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{post6.Id, post5.Id}, postList.Order)
|
|
assert.Equal(t, map[string]*model.Post{
|
|
post2.Id: post2,
|
|
post5.Id: post5,
|
|
post6.Id: post6,
|
|
}, postList.Posts)
|
|
})
|
|
})
|
|
t.Run("with threads (collapsedThreads)", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channelID := channel1.Id
|
|
userID := model.NewId()
|
|
|
|
// This creates a series of posts that looks like:
|
|
// post1
|
|
// post2
|
|
// post3 (in response to post1)
|
|
// post4 (in response to post2)
|
|
// post5
|
|
// post6 (in response to post2)
|
|
|
|
post1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "post1",
|
|
})
|
|
require.NoError(t, err)
|
|
post1.ReplyCount = 1
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post2, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "post2",
|
|
})
|
|
require.NoError(t, err)
|
|
post2.ReplyCount = 2
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post3, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
RootId: post1.Id,
|
|
Message: "post3",
|
|
})
|
|
require.NoError(t, err)
|
|
post3.ReplyCount = 1
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post4, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
RootId: post2.Id,
|
|
Message: "post4",
|
|
})
|
|
require.NoError(t, err)
|
|
post4.ReplyCount = 2
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post5, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "post5",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post6, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
RootId: post2.Id,
|
|
Message: "post6",
|
|
})
|
|
post6.ReplyCount = 2
|
|
require.NoError(t, err)
|
|
|
|
// Adding a post to a thread changes the UpdateAt timestamp of the parent post
|
|
post1.UpdateAt = post3.UpdateAt
|
|
post2.UpdateAt = post6.UpdateAt
|
|
|
|
t.Run("should return each root post before a post", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsBefore(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: post4.Id, PerPage: 2, CollapsedThreads: true}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{post2.Id, post1.Id}, postList.Order)
|
|
})
|
|
|
|
t.Run("should return each root post before a post with limit", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsBefore(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: post4.Id, PerPage: 1, CollapsedThreads: true}, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{post2.Id}, postList.Order)
|
|
})
|
|
|
|
t.Run("should return each root after a post", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPostsAfter(rctx, model.GetPostsOptions{ChannelId: channelID, PostId: post4.Id, PerPage: 2, CollapsedThreads: true}, map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{post5.Id}, postList.Order)
|
|
})
|
|
})
|
|
}
|
|
|
|
func testPostStoreGetPostsSince(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t.Run("should return posts created after the given time", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channelID := channel1.Id
|
|
userID := model.NewId()
|
|
|
|
post1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
_, err = ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post3, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post4, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post5, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
RootId: post3.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post6, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
RootId: post1.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
postList, err := ss.Post().GetPostsSince(rctx, model.GetPostsSinceOptions{ChannelId: channelID, Time: post3.CreateAt}, false, map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{
|
|
post6.Id,
|
|
post5.Id,
|
|
post4.Id,
|
|
post3.Id,
|
|
post1.Id,
|
|
}, postList.Order)
|
|
|
|
assert.Len(t, postList.Posts, 5)
|
|
assert.NotNil(t, postList.Posts[post1.Id], "should return the parent post")
|
|
assert.NotNil(t, postList.Posts[post3.Id])
|
|
assert.NotNil(t, postList.Posts[post4.Id])
|
|
assert.NotNil(t, postList.Posts[post5.Id])
|
|
assert.NotNil(t, postList.Posts[post6.Id])
|
|
})
|
|
|
|
t.Run("should return empty list when nothing has changed", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channelID := channel1.Id
|
|
userID := model.NewId()
|
|
|
|
post1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
postList, err := ss.Post().GetPostsSince(rctx, model.GetPostsSinceOptions{ChannelId: channelID, Time: post1.CreateAt}, false, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{}, postList.Order)
|
|
assert.Empty(t, postList.Posts)
|
|
})
|
|
|
|
t.Run("should not cache a timestamp of 0 when nothing has changed", func(t *testing.T) {
|
|
ss.Post().ClearCaches()
|
|
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channelID := channel1.Id
|
|
userID := model.NewId()
|
|
|
|
post1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
// Make a request that returns no results
|
|
postList, err := ss.Post().GetPostsSince(rctx, model.GetPostsSinceOptions{ChannelId: channelID, Time: post1.CreateAt}, true, map[string]bool{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, model.NewPostList(), postList)
|
|
|
|
// And then ensure that it doesn't cause future requests to also return no results
|
|
postList, err = ss.Post().GetPostsSince(rctx, model.GetPostsSinceOptions{ChannelId: channelID, Time: post1.CreateAt - 1}, true, map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{post1.Id}, postList.Order)
|
|
|
|
assert.Len(t, postList.Posts, 1)
|
|
assert.NotNil(t, postList.Posts[post1.Id])
|
|
})
|
|
}
|
|
|
|
func testPostStoreGetPosts(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channelID := channel1.Id
|
|
userID := model.NewId()
|
|
|
|
post1, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post2, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post3, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post4, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post5, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
RootId: post3.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post6, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
RootId: post1.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
t.Run("should return the last posts created in a channel", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPosts(rctx, model.GetPostsOptions{ChannelId: channelID, Page: 0, PerPage: 30, SkipFetchThreads: false}, false, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{
|
|
post6.Id,
|
|
post5.Id,
|
|
post4.Id,
|
|
post3.Id,
|
|
post2.Id,
|
|
post1.Id,
|
|
}, postList.Order)
|
|
|
|
assert.Len(t, postList.Posts, 6)
|
|
assert.NotNil(t, postList.Posts[post1.Id])
|
|
assert.NotNil(t, postList.Posts[post2.Id])
|
|
assert.NotNil(t, postList.Posts[post3.Id])
|
|
assert.NotNil(t, postList.Posts[post4.Id])
|
|
assert.NotNil(t, postList.Posts[post5.Id])
|
|
assert.NotNil(t, postList.Posts[post6.Id])
|
|
})
|
|
|
|
t.Run("should return the last posts created in a channel and the threads and the reply count must be 0", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPosts(rctx, model.GetPostsOptions{ChannelId: channelID, Page: 0, PerPage: 2, SkipFetchThreads: false}, false, map[string]bool{})
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{
|
|
post6.Id,
|
|
post5.Id,
|
|
}, postList.Order)
|
|
|
|
assert.Len(t, postList.Posts, 4)
|
|
require.NotNil(t, postList.Posts[post1.Id])
|
|
require.NotNil(t, postList.Posts[post3.Id])
|
|
require.NotNil(t, postList.Posts[post5.Id])
|
|
require.NotNil(t, postList.Posts[post6.Id])
|
|
assert.Equal(t, int64(0), postList.Posts[post1.Id].ReplyCount)
|
|
assert.Equal(t, int64(0), postList.Posts[post3.Id].ReplyCount)
|
|
assert.Equal(t, int64(0), postList.Posts[post5.Id].ReplyCount)
|
|
assert.Equal(t, int64(0), postList.Posts[post6.Id].ReplyCount)
|
|
})
|
|
|
|
t.Run("should return the last posts created in a channel without the threads and the reply count must be correct", func(t *testing.T) {
|
|
postList, err := ss.Post().GetPosts(rctx, model.GetPostsOptions{ChannelId: channelID, Page: 0, PerPage: 2, SkipFetchThreads: true}, false, map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{
|
|
post6.Id,
|
|
post5.Id,
|
|
}, postList.Order)
|
|
|
|
assert.Len(t, postList.Posts, 4)
|
|
assert.NotNil(t, postList.Posts[post5.Id])
|
|
assert.NotNil(t, postList.Posts[post6.Id])
|
|
assert.Equal(t, int64(1), postList.Posts[post5.Id].ReplyCount)
|
|
assert.Equal(t, int64(1), postList.Posts[post6.Id].ReplyCount)
|
|
})
|
|
|
|
t.Run("should return all posts in a channel included deleted posts", func(t *testing.T) {
|
|
err := ss.Post().Delete(rctx, post1.Id, 1, userID)
|
|
require.NoError(t, err)
|
|
|
|
postList, err := ss.Post().GetPosts(rctx, model.GetPostsOptions{ChannelId: channelID, Page: 0, PerPage: 30, SkipFetchThreads: false, IncludeDeleted: true}, false, map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{
|
|
post6.Id,
|
|
post5.Id,
|
|
post4.Id,
|
|
post3.Id,
|
|
post2.Id,
|
|
post1.Id,
|
|
}, postList.Order)
|
|
|
|
assert.Len(t, postList.Posts, 6)
|
|
assert.NotNil(t, postList.Posts[post1.Id])
|
|
assert.NotNil(t, postList.Posts[post2.Id])
|
|
assert.NotNil(t, postList.Posts[post3.Id])
|
|
assert.NotNil(t, postList.Posts[post4.Id])
|
|
assert.NotNil(t, postList.Posts[post5.Id])
|
|
assert.NotNil(t, postList.Posts[post6.Id])
|
|
})
|
|
|
|
t.Run("should return all posts in a channel included deleted posts without threads", func(t *testing.T) {
|
|
err := ss.Post().Delete(rctx, post5.Id, 1, userID)
|
|
require.NoError(t, err)
|
|
|
|
postList, err := ss.Post().GetPosts(rctx, model.GetPostsOptions{ChannelId: channelID, Page: 0, PerPage: 30, SkipFetchThreads: true, IncludeDeleted: true}, false, map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{
|
|
post6.Id,
|
|
post5.Id,
|
|
post4.Id,
|
|
post3.Id,
|
|
post2.Id,
|
|
post1.Id,
|
|
}, postList.Order)
|
|
|
|
assert.Len(t, postList.Posts, 6)
|
|
assert.NotNil(t, postList.Posts[post5.Id])
|
|
assert.NotNil(t, postList.Posts[post6.Id])
|
|
assert.Equal(t, int64(1), postList.Posts[post5.Id].ReplyCount)
|
|
assert.Equal(t, int64(1), postList.Posts[post6.Id].ReplyCount)
|
|
})
|
|
|
|
t.Run("should return the lasts posts created in channel without include deleted posts", func(t *testing.T) {
|
|
err := ss.Post().Delete(rctx, post6.Id, 1, userID)
|
|
require.NoError(t, err)
|
|
|
|
postList, err := ss.Post().GetPosts(rctx, model.GetPostsOptions{ChannelId: channelID, Page: 0, PerPage: 30, SkipFetchThreads: true, IncludeDeleted: false}, false, map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, []string{
|
|
post4.Id,
|
|
post3.Id,
|
|
post2.Id,
|
|
}, postList.Order)
|
|
|
|
assert.Len(t, postList.Posts, 3)
|
|
assert.NotNil(t, postList.Posts[post2.Id])
|
|
assert.NotNil(t, postList.Posts[post3.Id])
|
|
assert.NotNil(t, postList.Posts[post4.Id])
|
|
})
|
|
}
|
|
|
|
func testPostStoreGetPostBeforeAfter(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channelID := channel1.Id
|
|
|
|
o0 := &model.Post{}
|
|
o0.ChannelId = channelID
|
|
o0.UserId = model.NewId()
|
|
o0.Message = NewTestID()
|
|
_, err = ss.Post().Save(rctx, o0)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channelID
|
|
o1.Type = model.PostTypeJoinChannel
|
|
o1.UserId = model.NewId()
|
|
o1.Message = "system_join_channel message"
|
|
_, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o0a := &model.Post{}
|
|
o0a.ChannelId = channelID
|
|
o0a.UserId = model.NewId()
|
|
o0a.Message = NewTestID()
|
|
o0a.RootId = o1.Id
|
|
_, err = ss.Post().Save(rctx, o0a)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o0b := &model.Post{}
|
|
o0b.ChannelId = channelID
|
|
o0b.UserId = model.NewId()
|
|
o0b.Message = "deleted message"
|
|
o0b.RootId = o1.Id
|
|
o0b.DeleteAt = 1
|
|
_, err = ss.Post().Save(rctx, o0b)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName2",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
otherChannelPost := &model.Post{}
|
|
otherChannelPost.ChannelId = channel2.Id
|
|
otherChannelPost.UserId = model.NewId()
|
|
otherChannelPost.Message = NewTestID()
|
|
_, err = ss.Post().Save(rctx, otherChannelPost)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = channelID
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
_, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o2a := &model.Post{}
|
|
o2a.ChannelId = channelID
|
|
o2a.UserId = model.NewId()
|
|
o2a.Message = NewTestID()
|
|
o2a.RootId = o2.Id
|
|
_, err = ss.Post().Save(rctx, o2a)
|
|
require.NoError(t, err)
|
|
|
|
rPostID1, err := ss.Post().GetPostIdBeforeTime(channelID, o0a.CreateAt, false)
|
|
require.Equal(t, rPostID1, o1.Id, "should return before post o1")
|
|
require.NoError(t, err)
|
|
|
|
rPostID1, err = ss.Post().GetPostIdAfterTime(channelID, o0b.CreateAt, false)
|
|
require.Equal(t, rPostID1, o2.Id, "should return before post o2")
|
|
require.NoError(t, err)
|
|
|
|
rPost1, err := ss.Post().GetPostAfterTime(channelID, o0b.CreateAt, false)
|
|
require.Equal(t, rPost1.Id, o2.Id, "should return before post o2")
|
|
require.NoError(t, err)
|
|
|
|
rPostID2, err := ss.Post().GetPostIdBeforeTime(channelID, o0.CreateAt, false)
|
|
require.Empty(t, rPostID2, "should return no post")
|
|
require.NoError(t, err)
|
|
|
|
rPostID2, err = ss.Post().GetPostIdAfterTime(channelID, o0.CreateAt, false)
|
|
require.Equal(t, rPostID2, o1.Id, "should return before post o1")
|
|
require.NoError(t, err)
|
|
|
|
rPost2, err := ss.Post().GetPostAfterTime(channelID, o0.CreateAt, false)
|
|
require.Equal(t, rPost2.Id, o1.Id, "should return before post o1")
|
|
require.NoError(t, err)
|
|
|
|
rPostID3, err := ss.Post().GetPostIdBeforeTime(channelID, o2a.CreateAt, false)
|
|
require.Equal(t, rPostID3, o2.Id, "should return before post o2")
|
|
require.NoError(t, err)
|
|
|
|
rPostID3, err = ss.Post().GetPostIdAfterTime(channelID, o2a.CreateAt, false)
|
|
require.Empty(t, rPostID3, "should return no post")
|
|
require.NoError(t, err)
|
|
|
|
rPost3, err := ss.Post().GetPostAfterTime(channelID, o2a.CreateAt, false)
|
|
require.Empty(t, rPost3.Id, "should return no post")
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func testUserCountsWithPostsByDay(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t1 := &model.Team{}
|
|
t1.DisplayName = "DisplayName"
|
|
t1.Name = NewTestID()
|
|
t1.Email = MakeEmail()
|
|
t1.Type = model.TeamOpen
|
|
t1, err := ss.Team().Save(t1)
|
|
require.NoError(t, err)
|
|
|
|
c1 := &model.Channel{}
|
|
c1.TeamId = t1.Id
|
|
c1.DisplayName = "Channel2"
|
|
c1.Name = NewTestID()
|
|
c1.Type = model.ChannelTypeOpen
|
|
c1, nErr := ss.Channel().Save(rctx, c1, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = c1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.CreateAt = utils.MillisFromTime(utils.Yesterday())
|
|
o1.Message = NewTestID()
|
|
o1, nErr = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, nErr)
|
|
|
|
o1a := &model.Post{}
|
|
o1a.ChannelId = c1.Id
|
|
o1a.UserId = model.NewId()
|
|
o1a.CreateAt = o1.CreateAt
|
|
o1a.Message = NewTestID()
|
|
_, nErr = ss.Post().Save(rctx, o1a)
|
|
require.NoError(t, nErr)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = c1.Id
|
|
o2.UserId = model.NewId()
|
|
o2.CreateAt = o1.CreateAt - (1000 * 60 * 60 * 24)
|
|
o2.Message = NewTestID()
|
|
o2, nErr = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, nErr)
|
|
|
|
o2a := &model.Post{}
|
|
o2a.ChannelId = c1.Id
|
|
o2a.UserId = o2.UserId
|
|
o2a.CreateAt = o1.CreateAt - (1000 * 60 * 60 * 24)
|
|
o2a.Message = NewTestID()
|
|
_, nErr = ss.Post().Save(rctx, o2a)
|
|
require.NoError(t, nErr)
|
|
|
|
r1, err := ss.Post().AnalyticsUserCountsWithPostsByDay(t1.Id)
|
|
require.NoError(t, err)
|
|
|
|
row1 := r1[0]
|
|
require.Equal(t, float64(2), row1.Value, "wrong value")
|
|
|
|
row2 := r1[1]
|
|
require.Equal(t, float64(1), row2.Value, "wrong value")
|
|
}
|
|
|
|
func testPostCountsByDay(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t1 := &model.Team{}
|
|
t1.DisplayName = "DisplayName"
|
|
t1.Name = NewTestID()
|
|
t1.Email = MakeEmail()
|
|
t1.Type = model.TeamOpen
|
|
t1, err := ss.Team().Save(t1)
|
|
require.NoError(t, err)
|
|
|
|
c1 := &model.Channel{}
|
|
c1.TeamId = t1.Id
|
|
c1.DisplayName = "Channel2"
|
|
c1.Name = NewTestID()
|
|
c1.Type = model.ChannelTypeOpen
|
|
c1, nErr := ss.Channel().Save(rctx, c1, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = c1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.CreateAt = utils.MillisFromTime(utils.Yesterday())
|
|
o1.Message = NewTestID()
|
|
o1.Hashtags = "hashtag"
|
|
o1, nErr = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, nErr)
|
|
|
|
o1a := &model.Post{}
|
|
o1a.ChannelId = c1.Id
|
|
o1a.UserId = model.NewId()
|
|
o1a.CreateAt = o1.CreateAt
|
|
o1a.Message = NewTestID()
|
|
o1a.FileIds = []string{"fileId1"}
|
|
_, nErr = ss.Post().Save(rctx, o1a)
|
|
require.NoError(t, nErr)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = c1.Id
|
|
o2.UserId = model.NewId()
|
|
o2.CreateAt = o1.CreateAt - (1000 * 60 * 60 * 24 * 2)
|
|
o2.Message = NewTestID()
|
|
o2.Filenames = []string{"filename1"}
|
|
o2, nErr = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, nErr)
|
|
|
|
o2a := &model.Post{}
|
|
o2a.ChannelId = c1.Id
|
|
o2a.UserId = o2.UserId
|
|
o2a.CreateAt = o1.CreateAt - (1000 * 60 * 60 * 24 * 2)
|
|
o2a.Message = NewTestID()
|
|
o2a.Hashtags = "hashtag"
|
|
o2a.FileIds = []string{"fileId2"}
|
|
_, nErr = ss.Post().Save(rctx, o2a)
|
|
require.NoError(t, nErr)
|
|
|
|
bot1 := &model.Bot{
|
|
Username: "username",
|
|
Description: "a bot",
|
|
OwnerId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
}
|
|
_, nErr = ss.Bot().Save(bot1)
|
|
require.NoError(t, nErr)
|
|
|
|
b1 := &model.Post{}
|
|
b1.Message = "bot message one"
|
|
b1.ChannelId = c1.Id
|
|
b1.UserId = bot1.UserId
|
|
b1.CreateAt = utils.MillisFromTime(utils.Yesterday())
|
|
_, nErr = ss.Post().Save(rctx, b1)
|
|
require.NoError(t, nErr)
|
|
|
|
b1a := &model.Post{}
|
|
b1a.Message = "bot message two"
|
|
b1a.ChannelId = c1.Id
|
|
b1a.UserId = bot1.UserId
|
|
b1a.CreateAt = utils.MillisFromTime(utils.Yesterday()) - (1000 * 60 * 60 * 24 * 2)
|
|
_, nErr = ss.Post().Save(rctx, b1a)
|
|
require.NoError(t, nErr)
|
|
|
|
require.NoError(t, ss.Post().RefreshPostStats())
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
// summary of posts
|
|
// yesterday - 2 non-bot user posts, 1 bot user post
|
|
// 3 days ago - 2 non-bot user posts, 1 bot user post
|
|
|
|
// last 31 days, all users (including bots)
|
|
postCountsOptions := &model.AnalyticsPostCountsOptions{TeamId: t1.Id, BotsOnly: false, YesterdayOnly: false}
|
|
r1, err := ss.Post().AnalyticsPostCountsByDay(postCountsOptions)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, float64(3), r1[0].Value)
|
|
assert.Equal(t, float64(3), r1[1].Value)
|
|
assert.Equal(t, utils.Yesterday().Format("2006-01-02"), r1[0].Name)
|
|
assert.Equal(t, utils.Yesterday().Add(-48*time.Hour).Format("2006-01-02"), r1[1].Name)
|
|
|
|
// last 31 days, bots only
|
|
postCountsOptions = &model.AnalyticsPostCountsOptions{TeamId: t1.Id, BotsOnly: true, YesterdayOnly: false}
|
|
r1, err = ss.Post().AnalyticsPostCountsByDay(postCountsOptions)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, float64(1), r1[0].Value)
|
|
assert.Equal(t, float64(1), r1[1].Value)
|
|
assert.Equal(t, utils.Yesterday().Format("2006-01-02"), r1[0].Name)
|
|
assert.Equal(t, utils.Yesterday().Add(-48*time.Hour).Format("2006-01-02"), r1[1].Name)
|
|
|
|
// yesterday only, all users (including bots)
|
|
postCountsOptions = &model.AnalyticsPostCountsOptions{TeamId: t1.Id, BotsOnly: false, YesterdayOnly: true}
|
|
r1, err = ss.Post().AnalyticsPostCountsByDay(postCountsOptions)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, float64(3), r1[0].Value)
|
|
assert.Equal(t, utils.Yesterday().Format("2006-01-02"), r1[0].Name)
|
|
|
|
// yesterday only, bots only
|
|
postCountsOptions = &model.AnalyticsPostCountsOptions{TeamId: t1.Id, BotsOnly: true, YesterdayOnly: true}
|
|
r1, err = ss.Post().AnalyticsPostCountsByDay(postCountsOptions)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, float64(1), r1[0].Value)
|
|
assert.Equal(t, utils.Yesterday().Format("2006-01-02"), r1[0].Name)
|
|
}
|
|
|
|
func testPostCounts(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
now := time.Now()
|
|
twentyMinAgo := now.Add(-20 * time.Minute).UnixMilli()
|
|
fifteenMinAgo := now.Add(-15 * time.Minute).UnixMilli()
|
|
tenMinAgo := now.Add(-10 * time.Minute).UnixMilli()
|
|
|
|
t1 := &model.Team{}
|
|
t1.DisplayName = "DisplayName"
|
|
t1.Name = NewTestID()
|
|
t1.Email = MakeEmail()
|
|
t1.Type = model.TeamOpen
|
|
t1, err := ss.Team().Save(t1)
|
|
require.NoError(t, err)
|
|
|
|
c1 := &model.Channel{}
|
|
c1.TeamId = t1.Id
|
|
c1.DisplayName = "Channel2"
|
|
c1.Name = NewTestID()
|
|
c1.Type = model.ChannelTypeOpen
|
|
c1, nErr := ss.Channel().Save(rctx, c1, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
// system post
|
|
p1 := &model.Post{}
|
|
p1.Type = "system_add_to_channel"
|
|
p1.ChannelId = c1.Id
|
|
p1.UserId = model.NewId()
|
|
p1.Message = NewTestID()
|
|
p1.CreateAt = twentyMinAgo
|
|
p1.UpdateAt = twentyMinAgo
|
|
_, nErr = ss.Post().Save(rctx, p1)
|
|
require.NoError(t, nErr)
|
|
|
|
p2 := &model.Post{}
|
|
p2.ChannelId = c1.Id
|
|
p2.UserId = model.NewId()
|
|
p2.Message = NewTestID()
|
|
p2.Hashtags = "hashtag"
|
|
p2.CreateAt = twentyMinAgo
|
|
p2.UpdateAt = twentyMinAgo
|
|
p2, nErr = ss.Post().Save(rctx, p2)
|
|
require.NoError(t, nErr)
|
|
|
|
p3 := &model.Post{}
|
|
p3.ChannelId = c1.Id
|
|
p3.UserId = model.NewId()
|
|
p3.Message = NewTestID()
|
|
p3.FileIds = []string{"fileId1"}
|
|
p3.CreateAt = twentyMinAgo
|
|
p3.UpdateAt = twentyMinAgo
|
|
_, nErr = ss.Post().Save(rctx, p3)
|
|
require.NoError(t, nErr)
|
|
|
|
p4 := &model.Post{}
|
|
p4.ChannelId = c1.Id
|
|
p4.UserId = model.NewId()
|
|
p4.Message = NewTestID()
|
|
p4.Filenames = []string{"filename1"}
|
|
p4.CreateAt = tenMinAgo
|
|
p4.UpdateAt = tenMinAgo
|
|
p4, nErr = ss.Post().Save(rctx, p4)
|
|
require.NoError(t, nErr)
|
|
|
|
p5 := &model.Post{}
|
|
p5.ChannelId = c1.Id
|
|
p5.UserId = p4.UserId
|
|
p5.Message = NewTestID()
|
|
p5.Hashtags = "hashtag"
|
|
p5.FileIds = []string{"fileId2"}
|
|
p5.CreateAt = tenMinAgo
|
|
p5.UpdateAt = tenMinAgo
|
|
_, nErr = ss.Post().Save(rctx, p5)
|
|
require.NoError(t, nErr)
|
|
|
|
bot1 := &model.Bot{
|
|
Username: "username",
|
|
Description: "a bot",
|
|
OwnerId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
}
|
|
_, nErr = ss.Bot().Save(bot1)
|
|
require.NoError(t, nErr)
|
|
|
|
p6 := &model.Post{}
|
|
p6.Message = "bot message one"
|
|
p6.ChannelId = c1.Id
|
|
p6.UserId = bot1.UserId
|
|
p6.CreateAt = twentyMinAgo
|
|
p6.UpdateAt = twentyMinAgo
|
|
_, nErr = ss.Post().Save(rctx, p6)
|
|
require.NoError(t, nErr)
|
|
|
|
p7 := &model.Post{}
|
|
p7.Message = "bot message two"
|
|
p7.ChannelId = c1.Id
|
|
p7.UserId = bot1.UserId
|
|
p7.CreateAt = tenMinAgo
|
|
p7.UpdateAt = tenMinAgo
|
|
_, nErr = ss.Post().Save(rctx, p7)
|
|
require.NoError(t, nErr)
|
|
|
|
require.NoError(t, ss.Post().RefreshPostStats())
|
|
|
|
// total across all teams
|
|
c, err := ss.Post().AnalyticsPostCount(&model.PostCountOptions{})
|
|
require.NoError(t, err)
|
|
assert.GreaterOrEqual(t, c, int64(7))
|
|
|
|
// total for single team
|
|
c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(7), c)
|
|
|
|
c, err = ss.Post().AnalyticsPostCountByTeam(t1.Id)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(7), c)
|
|
|
|
// with files
|
|
c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, MustHaveFile: true})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(3), c)
|
|
|
|
// with hashtags
|
|
c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, MustHaveHashtag: true})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), c)
|
|
|
|
// with hashtags and files
|
|
c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, MustHaveFile: true, MustHaveHashtag: true})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), c)
|
|
|
|
// excluding system posts
|
|
c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, ExcludeSystemPosts: true})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(6), c)
|
|
|
|
// before update_at time
|
|
c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, SinceUpdateAt: fifteenMinAgo})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(3), c)
|
|
|
|
// equal to update_at time
|
|
c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, SinceUpdateAt: tenMinAgo})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(3), c)
|
|
|
|
// since update_at and since post id
|
|
tenMinAgoIDs := []string{p4.Id, p5.Id, p7.Id}
|
|
sort.Strings(tenMinAgoIDs)
|
|
c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, SinceUpdateAt: tenMinAgo, SincePostID: tenMinAgoIDs[0]})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), c)
|
|
|
|
// delete 1 post
|
|
err = ss.Post().Delete(rctx, p2.Id, 1, p2.UserId)
|
|
require.NoError(t, err)
|
|
|
|
// total for single team with the deleted post excluded
|
|
c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, ExcludeDeleted: true})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(6), c)
|
|
|
|
// total users only posts for single team with the deleted post excluded
|
|
c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, ExcludeDeleted: true, UsersPostsOnly: true})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(3), c)
|
|
}
|
|
|
|
func testPostStoreGetFlaggedPostsForTeam(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
c1 := &model.Channel{}
|
|
c1.TeamId = model.NewId()
|
|
c1.DisplayName = "Channel1"
|
|
c1.Name = NewTestID()
|
|
c1.Type = model.ChannelTypeOpen
|
|
c1, err := ss.Channel().Save(rctx, c1, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = c1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = o1.ChannelId
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3.DeleteAt = 1
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
m0 := &model.ChannelMember{}
|
|
m0.ChannelId = c1.Id
|
|
m0.UserId = o1.UserId
|
|
m0.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
_, err = ss.Channel().SaveMember(rctx, m0)
|
|
require.NoError(t, err)
|
|
|
|
teamID := model.NewId()
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName2",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o4 := &model.Post{}
|
|
o4.ChannelId = channel2.Id
|
|
o4.UserId = model.NewId()
|
|
o4.Message = NewTestID()
|
|
o4, err = ss.Post().Save(rctx, o4)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
c2 := &model.Channel{}
|
|
c2.DisplayName = "DMChannel1"
|
|
c2.Name = model.GetDMNameFromIds(NewTestID(), NewTestID())
|
|
c2.Type = model.ChannelTypeDirect
|
|
|
|
m1 := &model.ChannelMember{}
|
|
m1.ChannelId = c2.Id
|
|
m1.UserId = o1.UserId
|
|
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
|
|
m2 := &model.ChannelMember{}
|
|
m2.ChannelId = c2.Id
|
|
m2.UserId = model.NewId()
|
|
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
|
|
c2, err = ss.Channel().SaveDirectChannel(rctx, c2, m1, m2)
|
|
require.NoError(t, err)
|
|
|
|
o5 := &model.Post{}
|
|
o5.ChannelId = c2.Id
|
|
o5.UserId = m2.UserId
|
|
o5.Message = NewTestID()
|
|
o5, err = ss.Post().Save(rctx, o5)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
// Post on channel where user is not a member
|
|
channel3, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName3",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o6 := &model.Post{}
|
|
o6.ChannelId = channel3.Id
|
|
o6.UserId = m2.UserId
|
|
o6.Message = NewTestID()
|
|
o6, err = ss.Post().Save(rctx, o6)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
r1, err := ss.Post().GetFlaggedPosts(o1.ChannelId, 0, 2)
|
|
require.NoError(t, err)
|
|
|
|
require.Empty(t, r1.Order, "should be empty")
|
|
|
|
preferences := model.Preferences{
|
|
{
|
|
UserId: o1.UserId,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: o1.Id,
|
|
Value: "true",
|
|
},
|
|
}
|
|
|
|
err = ss.Preference().Save(preferences)
|
|
require.NoError(t, err)
|
|
|
|
r2, err := ss.Post().GetFlaggedPostsForTeam(o1.UserId, c1.TeamId, 0, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, r2.Order, 1, "should have 1 post")
|
|
|
|
preferences = model.Preferences{
|
|
{
|
|
UserId: o1.UserId,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: o2.Id,
|
|
Value: "true",
|
|
},
|
|
}
|
|
|
|
err = ss.Preference().Save(preferences)
|
|
require.NoError(t, err)
|
|
|
|
r3, err := ss.Post().GetFlaggedPostsForTeam(o1.UserId, c1.TeamId, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, r3.Order, 1, "should have 1 post")
|
|
|
|
r3, err = ss.Post().GetFlaggedPostsForTeam(o1.UserId, c1.TeamId, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, r3.Order, 1, "should have 1 post")
|
|
|
|
r3, err = ss.Post().GetFlaggedPostsForTeam(o1.UserId, c1.TeamId, 1000, 10)
|
|
require.NoError(t, err)
|
|
require.Empty(t, r3.Order, "should be empty")
|
|
|
|
r4, err := ss.Post().GetFlaggedPostsForTeam(o1.UserId, c1.TeamId, 0, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, r4.Order, 2, "should have 2 posts")
|
|
|
|
preferences = model.Preferences{
|
|
{
|
|
UserId: o1.UserId,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: o3.Id,
|
|
Value: "true",
|
|
},
|
|
}
|
|
|
|
err = ss.Preference().Save(preferences)
|
|
require.NoError(t, err)
|
|
|
|
r4, err = ss.Post().GetFlaggedPostsForTeam(o1.UserId, c1.TeamId, 0, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, r4.Order, 2, "should have 2 posts")
|
|
|
|
preferences = model.Preferences{
|
|
{
|
|
UserId: o1.UserId,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: o4.Id,
|
|
Value: "true",
|
|
},
|
|
}
|
|
err = ss.Preference().Save(preferences)
|
|
require.NoError(t, err)
|
|
|
|
r4, err = ss.Post().GetFlaggedPostsForTeam(o1.UserId, c1.TeamId, 0, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, r4.Order, 2, "should have 2 posts")
|
|
|
|
r4, err = ss.Post().GetFlaggedPostsForTeam(o1.UserId, model.NewId(), 0, 2)
|
|
require.NoError(t, err)
|
|
require.Empty(t, r4.Order, "should have 0 posts")
|
|
|
|
preferences = model.Preferences{
|
|
{
|
|
UserId: o1.UserId,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: o5.Id,
|
|
Value: "true",
|
|
},
|
|
}
|
|
err = ss.Preference().Save(preferences)
|
|
require.NoError(t, err)
|
|
|
|
r4, err = ss.Post().GetFlaggedPostsForTeam(o1.UserId, c1.TeamId, 0, 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, r4.Order, 3, "should have 3 posts")
|
|
|
|
preferences = model.Preferences{
|
|
{
|
|
UserId: o1.UserId,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: o6.Id,
|
|
Value: "true",
|
|
},
|
|
}
|
|
err = ss.Preference().Save(preferences)
|
|
require.NoError(t, err)
|
|
|
|
r4, err = ss.Post().GetFlaggedPostsForTeam(o1.UserId, c1.TeamId, 0, 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, r4.Order, 3, "should have 3 posts")
|
|
|
|
// Manually truncate Channels table until testlib can handle cleanups
|
|
s.GetMaster().Exec("TRUNCATE Channels")
|
|
}
|
|
|
|
func testPostStoreGetFlaggedPosts(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
c1 := &model.Channel{}
|
|
c1.TeamId = model.NewId()
|
|
c1.DisplayName = "Channel1"
|
|
c1.Name = NewTestID()
|
|
c1.Type = model.ChannelTypeOpen
|
|
c1, err := ss.Channel().Save(rctx, c1, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = c1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = o1.ChannelId
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3.DeleteAt = 1
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
// Post on channel where user is not a member
|
|
teamID := model.NewId()
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName2",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o4 := &model.Post{}
|
|
o4.ChannelId = channel2.Id
|
|
o4.UserId = model.NewId()
|
|
o4.Message = NewTestID()
|
|
o4, err = ss.Post().Save(rctx, o4)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
m0 := &model.ChannelMember{}
|
|
m0.ChannelId = o1.ChannelId
|
|
m0.UserId = o1.UserId
|
|
m0.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
_, err = ss.Channel().SaveMember(rctx, m0)
|
|
require.NoError(t, err)
|
|
|
|
r1, err := ss.Post().GetFlaggedPosts(o1.UserId, 0, 2)
|
|
require.NoError(t, err)
|
|
require.Empty(t, r1.Order, "should be empty")
|
|
|
|
preferences := model.Preferences{
|
|
{
|
|
UserId: o1.UserId,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: o1.Id,
|
|
Value: "true",
|
|
},
|
|
}
|
|
|
|
nErr := ss.Preference().Save(preferences)
|
|
require.NoError(t, nErr)
|
|
|
|
r2, err := ss.Post().GetFlaggedPosts(o1.UserId, 0, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, r2.Order, 1, "should have 1 post")
|
|
|
|
preferences = model.Preferences{
|
|
{
|
|
UserId: o1.UserId,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: o2.Id,
|
|
Value: "true",
|
|
},
|
|
}
|
|
|
|
nErr = ss.Preference().Save(preferences)
|
|
require.NoError(t, nErr)
|
|
|
|
r3, err := ss.Post().GetFlaggedPosts(o1.UserId, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, r3.Order, 1, "should have 1 post")
|
|
|
|
r3, err = ss.Post().GetFlaggedPosts(o1.UserId, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, r3.Order, 1, "should have 1 post")
|
|
|
|
r3, err = ss.Post().GetFlaggedPosts(o1.UserId, 1000, 10)
|
|
require.NoError(t, err)
|
|
require.Empty(t, r3.Order, "should be empty")
|
|
|
|
r4, err := ss.Post().GetFlaggedPosts(o1.UserId, 0, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, r4.Order, 2, "should have 2 posts")
|
|
|
|
preferences = model.Preferences{
|
|
{
|
|
UserId: o1.UserId,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: o3.Id,
|
|
Value: "true",
|
|
},
|
|
}
|
|
|
|
nErr = ss.Preference().Save(preferences)
|
|
require.NoError(t, nErr)
|
|
|
|
r4, err = ss.Post().GetFlaggedPosts(o1.UserId, 0, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, r4.Order, 2, "should have 2 posts")
|
|
|
|
preferences = model.Preferences{
|
|
{
|
|
UserId: o1.UserId,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: o4.Id,
|
|
Value: "true",
|
|
},
|
|
}
|
|
|
|
nErr = ss.Preference().Save(preferences)
|
|
require.NoError(t, nErr)
|
|
|
|
r4, err = ss.Post().GetFlaggedPosts(o1.UserId, 0, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, r4.Order, 2, "should have 2 posts")
|
|
}
|
|
|
|
func testPostStoreGetFlaggedPostsForChannel(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
c1 := &model.Channel{}
|
|
c1.TeamId = model.NewId()
|
|
c1.DisplayName = "Channel1"
|
|
c1.Name = NewTestID()
|
|
c1.Type = model.ChannelTypeOpen
|
|
c1, err := ss.Channel().Save(rctx, c1, -1)
|
|
require.NoError(t, err)
|
|
|
|
c2 := &model.Channel{}
|
|
c2.TeamId = model.NewId()
|
|
c2.DisplayName = "Channel2"
|
|
c2.Name = NewTestID()
|
|
c2.Type = model.ChannelTypeOpen
|
|
c2, err = ss.Channel().Save(rctx, c2, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = c1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
// deleted post
|
|
teamID := model.NewId()
|
|
channel3, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName3",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = channel3.Id
|
|
o3.UserId = o1.ChannelId
|
|
o3.Message = NewTestID()
|
|
o3.DeleteAt = 1
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
o4 := &model.Post{}
|
|
o4.ChannelId = c2.Id
|
|
o4.UserId = model.NewId()
|
|
o4.Message = NewTestID()
|
|
o4, err = ss.Post().Save(rctx, o4)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
// Post on channel where user is not a member
|
|
channel4, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName4",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o5 := &model.Post{}
|
|
o5.ChannelId = channel4.Id
|
|
o5.UserId = model.NewId()
|
|
o5.Message = NewTestID()
|
|
o5, err = ss.Post().Save(rctx, o5)
|
|
require.NoError(t, err)
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
m1 := &model.ChannelMember{}
|
|
m1.ChannelId = o1.ChannelId
|
|
m1.UserId = o1.UserId
|
|
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
_, err = ss.Channel().SaveMember(rctx, m1)
|
|
require.NoError(t, err)
|
|
|
|
m2 := &model.ChannelMember{}
|
|
m2.ChannelId = o4.ChannelId
|
|
m2.UserId = o1.UserId
|
|
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
_, err = ss.Channel().SaveMember(rctx, m2)
|
|
require.NoError(t, err)
|
|
|
|
r, err := ss.Post().GetFlaggedPostsForChannel(o1.UserId, o1.ChannelId, 0, 10)
|
|
require.NoError(t, err)
|
|
require.Empty(t, r.Order, "should be empty")
|
|
|
|
preference := model.Preference{
|
|
UserId: o1.UserId,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: o1.Id,
|
|
Value: "true",
|
|
}
|
|
|
|
nErr := ss.Preference().Save(model.Preferences{preference})
|
|
require.NoError(t, nErr)
|
|
|
|
r, err = ss.Post().GetFlaggedPostsForChannel(o1.UserId, o1.ChannelId, 0, 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, r.Order, 1, "should have 1 post")
|
|
|
|
preference.Name = o2.Id
|
|
nErr = ss.Preference().Save(model.Preferences{preference})
|
|
require.NoError(t, nErr)
|
|
|
|
preference.Name = o3.Id
|
|
nErr = ss.Preference().Save(model.Preferences{preference})
|
|
require.NoError(t, nErr)
|
|
|
|
r, err = ss.Post().GetFlaggedPostsForChannel(o1.UserId, o1.ChannelId, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, r.Order, 1, "should have 1 post")
|
|
|
|
r, err = ss.Post().GetFlaggedPostsForChannel(o1.UserId, o1.ChannelId, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, r.Order, 1, "should have 1 post")
|
|
|
|
r, err = ss.Post().GetFlaggedPostsForChannel(o1.UserId, o1.ChannelId, 1000, 10)
|
|
require.NoError(t, err)
|
|
require.Empty(t, r.Order, "should be empty")
|
|
|
|
r, err = ss.Post().GetFlaggedPostsForChannel(o1.UserId, o1.ChannelId, 0, 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, r.Order, 2, "should have 2 posts")
|
|
|
|
preference.Name = o4.Id
|
|
nErr = ss.Preference().Save(model.Preferences{preference})
|
|
require.NoError(t, nErr)
|
|
|
|
r, err = ss.Post().GetFlaggedPostsForChannel(o1.UserId, o4.ChannelId, 0, 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, r.Order, 1, "should have 1 posts")
|
|
|
|
preference.Name = o5.Id
|
|
nErr = ss.Preference().Save(model.Preferences{preference})
|
|
require.NoError(t, nErr)
|
|
|
|
r, err = ss.Post().GetFlaggedPostsForChannel(o1.UserId, o5.ChannelId, 0, 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, r.Order, 0, "should have 0 posts")
|
|
}
|
|
|
|
func testPostStoreGetPostsCreatedAt(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
createTime := model.GetMillis() + 1
|
|
|
|
o0 := &model.Post{}
|
|
o0.ChannelId = channel1.Id
|
|
o0.UserId = model.NewId()
|
|
o0.Message = NewTestID()
|
|
o0.CreateAt = createTime
|
|
o0, err = ss.Post().Save(rctx, o0)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = o0.ChannelId
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1.CreateAt = createTime
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2.RootId = o1.Id
|
|
o2.CreateAt = createTime + 1
|
|
_, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName2",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = channel2.Id
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3.CreateAt = createTime
|
|
_, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
r1, _ := ss.Post().GetPostsCreatedAt(o1.ChannelId, createTime)
|
|
assert.Equal(t, 2, len(r1))
|
|
}
|
|
|
|
func testPostStoreOverwriteMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channel1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2.RootId = o1.Id
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = o1.ChannelId
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName2",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o4, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel2.Id,
|
|
UserId: model.NewId(),
|
|
Message: model.NewId(),
|
|
Filenames: []string{"test"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
channel3, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName3",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
o5, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel3.Id,
|
|
UserId: model.NewId(),
|
|
Message: model.NewId(),
|
|
Filenames: []string{"test2", "test3"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
r1, err := ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro1 := r1.Posts[o1.Id]
|
|
|
|
r2, err := ss.Post().Get(rctx, o2.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro2 := r2.Posts[o2.Id]
|
|
|
|
r3, err := ss.Post().Get(rctx, o3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro3 := r3.Posts[o3.Id]
|
|
|
|
r4, err := ss.Post().Get(rctx, o4.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro4 := r4.Posts[o4.Id]
|
|
|
|
r5, err := ss.Post().Get(rctx, o5.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro5 := r5.Posts[o5.Id]
|
|
|
|
require.Equal(t, ro1.Message, o1.Message, "Failed to save/get")
|
|
require.Equal(t, ro2.Message, o2.Message, "Failed to save/get")
|
|
require.Equal(t, ro3.Message, o3.Message, "Failed to save/get")
|
|
require.Equal(t, ro4.Message, o4.Message, "Failed to save/get")
|
|
require.Equal(t, ro4.Filenames, o4.Filenames, "Failed to save/get")
|
|
require.Equal(t, ro5.Message, o5.Message, "Failed to save/get")
|
|
require.Equal(t, ro5.Filenames, o5.Filenames, "Failed to save/get")
|
|
|
|
t.Run("overwrite changing message", func(t *testing.T) {
|
|
o1a := ro1.Clone()
|
|
o1a.Message = ro1.Message + "BBBBBBBBBB"
|
|
|
|
o2a := ro2.Clone()
|
|
o2a.Message = ro2.Message + "DDDDDDD"
|
|
|
|
o3a := ro3.Clone()
|
|
o3a.Message = ro3.Message + "WWWWWWW"
|
|
|
|
_, errIdx, err := ss.Post().OverwriteMultiple(rctx, []*model.Post{o1a, o2a, o3a})
|
|
require.NoError(t, err)
|
|
require.Equal(t, -1, errIdx)
|
|
|
|
r1, nErr := ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, nErr)
|
|
ro1a := r1.Posts[o1.Id]
|
|
|
|
r2, nErr = ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, nErr)
|
|
ro2a := r2.Posts[o2.Id]
|
|
|
|
r3, nErr = ss.Post().Get(rctx, o3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, nErr)
|
|
ro3a := r3.Posts[o3.Id]
|
|
|
|
assert.Equal(t, ro1a.Message, o1a.Message, "Failed to overwrite/get")
|
|
assert.Equal(t, ro2a.Message, o2a.Message, "Failed to overwrite/get")
|
|
assert.Equal(t, ro3a.Message, o3a.Message, "Failed to overwrite/get")
|
|
})
|
|
|
|
t.Run("overwrite clearing filenames", func(t *testing.T) {
|
|
o4a := ro4.Clone()
|
|
o4a.Filenames = []string{}
|
|
o4a.FileIds = []string{model.NewId()}
|
|
|
|
o5a := ro5.Clone()
|
|
o5a.Filenames = []string{}
|
|
o5a.FileIds = []string{}
|
|
|
|
_, errIdx, err := ss.Post().OverwriteMultiple(rctx, []*model.Post{o4a, o5a})
|
|
require.NoError(t, err)
|
|
require.Equal(t, -1, errIdx)
|
|
|
|
r4, nErr := ss.Post().Get(rctx, o4.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, nErr)
|
|
ro4a := r4.Posts[o4.Id]
|
|
|
|
r5, nErr = ss.Post().Get(rctx, o5.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, nErr)
|
|
ro5a := r5.Posts[o5.Id]
|
|
|
|
require.Empty(t, ro4a.Filenames, "Failed to clear Filenames")
|
|
require.Len(t, ro4a.FileIds, 1, "Failed to set FileIds")
|
|
require.Empty(t, ro5a.Filenames, "Failed to clear Filenames")
|
|
require.Empty(t, ro5a.FileIds, "Failed to set FileIds")
|
|
})
|
|
}
|
|
|
|
func testPostStoreOverwrite(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channel1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2.RootId = o1.Id
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = o1.ChannelId
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
channel2, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName2",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
o4, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel2.Id,
|
|
UserId: model.NewId(),
|
|
Message: model.NewId(),
|
|
Filenames: []string{"test"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
r1, err := ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro1 := r1.Posts[o1.Id]
|
|
|
|
r2, err := ss.Post().Get(rctx, o2.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro2 := r2.Posts[o2.Id]
|
|
|
|
r3, err := ss.Post().Get(rctx, o3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro3 := r3.Posts[o3.Id]
|
|
|
|
r4, err := ss.Post().Get(rctx, o4.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro4 := r4.Posts[o4.Id]
|
|
|
|
require.Equal(t, ro1.Message, o1.Message, "Failed to save/get")
|
|
require.Equal(t, ro2.Message, o2.Message, "Failed to save/get")
|
|
require.Equal(t, ro3.Message, o3.Message, "Failed to save/get")
|
|
require.Equal(t, ro4.Message, o4.Message, "Failed to save/get")
|
|
|
|
t.Run("overwrite changing message", func(t *testing.T) {
|
|
o1a := ro1.Clone()
|
|
o1a.Message = ro1.Message + "BBBBBBBBBB"
|
|
_, err = ss.Post().Overwrite(rctx, o1a)
|
|
require.NoError(t, err)
|
|
|
|
o2a := ro2.Clone()
|
|
o2a.Message = ro2.Message + "DDDDDDD"
|
|
_, err = ss.Post().Overwrite(rctx, o2a)
|
|
require.NoError(t, err)
|
|
|
|
o3a := ro3.Clone()
|
|
o3a.Message = ro3.Message + "WWWWWWW"
|
|
_, err = ss.Post().Overwrite(rctx, o3a)
|
|
require.NoError(t, err)
|
|
|
|
r1, err = ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro1a := r1.Posts[o1.Id]
|
|
|
|
r2, err = ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro2a := r2.Posts[o2.Id]
|
|
|
|
r3, err = ss.Post().Get(rctx, o3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro3a := r3.Posts[o3.Id]
|
|
|
|
assert.Equal(t, ro1a.Message, o1a.Message, "Failed to overwrite/get")
|
|
assert.Equal(t, ro2a.Message, o2a.Message, "Failed to overwrite/get")
|
|
assert.Equal(t, ro3a.Message, o3a.Message, "Failed to overwrite/get")
|
|
})
|
|
|
|
t.Run("overwrite clearing filenames", func(t *testing.T) {
|
|
o4a := ro4.Clone()
|
|
o4a.Filenames = []string{}
|
|
o4a.FileIds = []string{model.NewId()}
|
|
_, err = ss.Post().Overwrite(rctx, o4a)
|
|
require.NoError(t, err)
|
|
|
|
r4, err = ss.Post().Get(rctx, o4.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
|
|
ro4a := r4.Posts[o4.Id]
|
|
require.Empty(t, ro4a.Filenames, "Failed to clear Filenames")
|
|
require.Len(t, ro4a.FileIds, 1, "Failed to set FileIds")
|
|
})
|
|
}
|
|
|
|
func testPostStoreGetPostsByIds(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channel1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = o1.ChannelId
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
r1, err := ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro1 := r1.Posts[o1.Id]
|
|
|
|
r2, err := ss.Post().Get(rctx, o2.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro2 := r2.Posts[o2.Id]
|
|
|
|
r3, err := ss.Post().Get(rctx, o3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err)
|
|
ro3 := r3.Posts[o3.Id]
|
|
|
|
postIds := []string{
|
|
ro1.Id,
|
|
ro2.Id,
|
|
ro3.Id,
|
|
}
|
|
|
|
posts, err := ss.Post().GetPostsByIds(postIds)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts, 3, "Expected 3 posts in results. Got %v", len(posts))
|
|
|
|
err = ss.Post().Delete(rctx, ro1.Id, model.GetMillis(), "")
|
|
require.NoError(t, err)
|
|
|
|
posts, err = ss.Post().GetPostsByIds(postIds)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts, 3, "Expected 3 posts in results. Got %v", len(posts))
|
|
}
|
|
|
|
func testPostStoreGetPostsBatchForIndexing(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
c1 := &model.Channel{}
|
|
c1.TeamId = model.NewId()
|
|
c1.DisplayName = "Channel1"
|
|
c1.Name = NewTestID()
|
|
c1.Type = model.ChannelTypeOpen
|
|
c1, _ = ss.Channel().Save(rctx, c1, -1)
|
|
|
|
c2 := &model.Channel{}
|
|
c2.TeamId = model.NewId()
|
|
c2.DisplayName = "Channel2"
|
|
c2.Name = NewTestID()
|
|
c2.Type = model.ChannelTypeOpen
|
|
c2, _ = ss.Channel().Save(rctx, c2, -1)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = c1.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1, err := ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = c2.Id
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
_, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = c1.Id
|
|
o3.UserId = model.NewId()
|
|
o3.RootId = o1.Id
|
|
o3.Message = NewTestID()
|
|
_, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
// Getting all
|
|
r, err := ss.Post().GetPostsBatchForIndexing(o1.CreateAt-1, "", 100)
|
|
require.NoError(t, err)
|
|
require.Len(t, r, 3, "Expected 3 posts in results. Got %v", len(r))
|
|
|
|
// Testing pagination
|
|
r, err = ss.Post().GetPostsBatchForIndexing(o1.CreateAt-1, "", 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, r, 1, "Expected 1 post in results. Got %v", len(r))
|
|
|
|
r, err = ss.Post().GetPostsBatchForIndexing(r[0].CreateAt, r[0].Id, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, r, 1, "Expected 1 post in results. Got %v", len(r))
|
|
|
|
r, err = ss.Post().GetPostsBatchForIndexing(r[0].CreateAt, r[0].Id, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, r, 1, "Expected 1 post in results. Got %v", len(r))
|
|
|
|
r, err = ss.Post().GetPostsBatchForIndexing(r[0].CreateAt, r[0].Id, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, r, 0, "Expected 0 post in results. Got %v", len(r))
|
|
}
|
|
|
|
func testPostStorePermanentDeleteBatch(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
team, err := ss.Team().Save(&model.Team{
|
|
DisplayName: "DisplayName",
|
|
Name: "team" + model.NewId(),
|
|
Email: MakeEmail(),
|
|
Type: model.TeamOpen,
|
|
})
|
|
require.NoError(t, err)
|
|
channel, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: team.Id,
|
|
DisplayName: "DisplayName",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = channel.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1.CreateAt = 1000
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = channel.Id
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2.CreateAt = 1000
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
o3 := &model.Post{}
|
|
o3.ChannelId = channel.Id
|
|
o3.UserId = model.NewId()
|
|
o3.Message = NewTestID()
|
|
o3.CreateAt = 100000
|
|
o3, err = ss.Post().Save(rctx, o3)
|
|
require.NoError(t, err)
|
|
|
|
deleted, _, err := ss.Post().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{
|
|
Now: 0,
|
|
GlobalPolicyEndTime: 2000,
|
|
Limit: 1000,
|
|
}, model.RetentionPolicyCursor{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(2), deleted)
|
|
|
|
_, err = ss.Post().Get(rctx, o1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Should have not found post 1 after purge")
|
|
|
|
_, err = ss.Post().Get(rctx, o2.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Should have not found post 2 after purge")
|
|
|
|
_, err = ss.Post().Get(rctx, o3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err, "Should have found post 3 after purge")
|
|
|
|
rows, err := ss.RetentionPolicy().GetIdsForDeletionByTableName("Posts", 1000)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(rows))
|
|
require.Equal(t, 2, len(rows[0].Ids))
|
|
// Clean up retention ids table
|
|
deleted, err = ss.Reaction().DeleteOrphanedRowsByIds(rows[0])
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), deleted)
|
|
|
|
t.Run("with pagination", func(t *testing.T) {
|
|
for range 3 {
|
|
_, err = ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel.Id,
|
|
UserId: model.NewId(),
|
|
Message: "message",
|
|
CreateAt: 1,
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
cursor := model.RetentionPolicyCursor{}
|
|
|
|
deleted, cursor, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{
|
|
Now: 0,
|
|
GlobalPolicyEndTime: 2,
|
|
Limit: 2,
|
|
}, cursor)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(2), deleted)
|
|
|
|
rows, err = ss.RetentionPolicy().GetIdsForDeletionByTableName("Posts", 1000)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(rows))
|
|
require.Equal(t, 2, len(rows[0].Ids))
|
|
|
|
// Clean up retention ids table
|
|
deleted, err = ss.Reaction().DeleteOrphanedRowsByIds(rows[0])
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), deleted)
|
|
|
|
deleted, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{
|
|
Now: 0,
|
|
GlobalPolicyEndTime: 2,
|
|
Limit: 2,
|
|
}, cursor)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(1), deleted)
|
|
|
|
rows, err = ss.RetentionPolicy().GetIdsForDeletionByTableName("Posts", 1000)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(rows))
|
|
require.Equal(t, 1, len(rows[0].Ids))
|
|
|
|
// Clean up retention ids table
|
|
deleted, err = ss.Reaction().DeleteOrphanedRowsByIds(rows[0])
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), deleted)
|
|
})
|
|
|
|
t.Run("with data retention policies", func(t *testing.T) {
|
|
channelPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
|
RetentionPolicy: model.RetentionPolicy{
|
|
DisplayName: "DisplayName",
|
|
PostDurationDays: model.NewPointer(int64(30)),
|
|
},
|
|
ChannelIDs: []string{channel.Id},
|
|
})
|
|
require.NoError(t, err2)
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
UserId: model.NewId(),
|
|
Message: "message",
|
|
CreateAt: 1,
|
|
}
|
|
post, err2 = ss.Post().Save(rctx, post)
|
|
require.NoError(t, err2)
|
|
|
|
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{
|
|
Now: 0,
|
|
GlobalPolicyEndTime: 2000,
|
|
Limit: 1000,
|
|
}, model.RetentionPolicyCursor{})
|
|
require.NoError(t, err2)
|
|
_, err2 = ss.Post().Get(rctx, post.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err2, "global policy should have been ignored due to granular policy")
|
|
|
|
nowMillis := post.CreateAt + *channelPolicy.PostDurationDays*model.DayInMilliseconds + 1
|
|
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{
|
|
Now: nowMillis,
|
|
GlobalPolicyEndTime: 0,
|
|
Limit: 1000,
|
|
}, model.RetentionPolicyCursor{})
|
|
require.NoError(t, err2)
|
|
_, err2 = ss.Post().Get(rctx, post.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err2, "post should have been deleted by channel policy")
|
|
|
|
// Create a team policy which is stricter than the channel policy
|
|
teamPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
|
RetentionPolicy: model.RetentionPolicy{
|
|
DisplayName: "DisplayName",
|
|
PostDurationDays: model.NewPointer(int64(20)),
|
|
},
|
|
TeamIDs: []string{team.Id},
|
|
})
|
|
require.NoError(t, err2)
|
|
post.Id = ""
|
|
post, err2 = ss.Post().Save(rctx, post)
|
|
require.NoError(t, err2)
|
|
|
|
nowMillis = post.CreateAt + *teamPolicy.PostDurationDays*model.DayInMilliseconds + 1
|
|
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{
|
|
Now: nowMillis,
|
|
GlobalPolicyEndTime: 0,
|
|
Limit: 1000,
|
|
}, model.RetentionPolicyCursor{})
|
|
require.NoError(t, err2)
|
|
_, err2 = ss.Post().Get(rctx, post.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err2, "channel policy should have overridden team policy")
|
|
|
|
// Delete channel policy and re-run team policy
|
|
err2 = ss.RetentionPolicy().RemoveChannels(channelPolicy.ID, []string{channel.Id})
|
|
require.NoError(t, err2)
|
|
|
|
err2 = ss.RetentionPolicy().Delete(channelPolicy.ID)
|
|
require.NoError(t, err2)
|
|
|
|
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{
|
|
Now: nowMillis,
|
|
GlobalPolicyEndTime: 0,
|
|
Limit: 1000,
|
|
}, model.RetentionPolicyCursor{})
|
|
require.NoError(t, err2)
|
|
_, err2 = ss.Post().Get(rctx, post.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err2, "post should have been deleted by team policy")
|
|
|
|
err2 = ss.RetentionPolicy().RemoveTeams(teamPolicy.ID, []string{team.Id})
|
|
require.NoError(t, err2)
|
|
|
|
err2 = ss.RetentionPolicy().Delete(teamPolicy.ID)
|
|
require.NoError(t, err2)
|
|
|
|
// Clean up retention ids table
|
|
rows, err = ss.RetentionPolicy().GetIdsForDeletionByTableName("Posts", 1000)
|
|
require.NoError(t, err)
|
|
for _, row := range rows {
|
|
deleted, err = ss.Reaction().DeleteOrphanedRowsByIds(row)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), deleted)
|
|
}
|
|
})
|
|
|
|
t.Run("with channel, team and global policies", func(t *testing.T) {
|
|
c1 := &model.Channel{}
|
|
c1.TeamId = model.NewId()
|
|
c1.DisplayName = "Channel1"
|
|
c1.Name = NewTestID()
|
|
c1.Type = model.ChannelTypeOpen
|
|
c1, _ = ss.Channel().Save(rctx, c1, -1)
|
|
|
|
c2 := &model.Channel{}
|
|
c2.TeamId = model.NewId()
|
|
c2.DisplayName = "Channel2"
|
|
c2.Name = NewTestID()
|
|
c2.Type = model.ChannelTypeOpen
|
|
c2, _ = ss.Channel().Save(rctx, c2, -1)
|
|
|
|
channelPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
|
RetentionPolicy: model.RetentionPolicy{
|
|
DisplayName: "DisplayName",
|
|
PostDurationDays: model.NewPointer(int64(30)),
|
|
},
|
|
ChannelIDs: []string{c1.Id},
|
|
})
|
|
require.NoError(t, err2)
|
|
defer ss.RetentionPolicy().Delete(channelPolicy.ID)
|
|
teamPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
|
RetentionPolicy: model.RetentionPolicy{
|
|
DisplayName: "DisplayName",
|
|
PostDurationDays: model.NewPointer(int64(30)),
|
|
},
|
|
TeamIDs: []string{team.Id},
|
|
})
|
|
require.NoError(t, err2)
|
|
defer ss.RetentionPolicy().Delete(teamPolicy.ID)
|
|
|
|
// This one should be deleted by the channel policy
|
|
_, err2 = ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: c1.Id,
|
|
UserId: model.NewId(),
|
|
Message: "message",
|
|
CreateAt: 1,
|
|
})
|
|
require.NoError(t, err2)
|
|
// This one, by the team policy
|
|
_, err2 = ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channel.Id,
|
|
UserId: model.NewId(),
|
|
Message: "message",
|
|
CreateAt: 1,
|
|
})
|
|
require.NoError(t, err2)
|
|
// This one, by the global policy
|
|
_, err2 = ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: c2.Id,
|
|
UserId: model.NewId(),
|
|
Message: "message",
|
|
CreateAt: 1,
|
|
})
|
|
require.NoError(t, err2)
|
|
|
|
nowMillis := int64(1 + 30*model.DayInMilliseconds + 1)
|
|
deleted, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{
|
|
Now: nowMillis,
|
|
GlobalPolicyEndTime: 2,
|
|
Limit: 1000,
|
|
}, model.RetentionPolicyCursor{})
|
|
require.NoError(t, err2)
|
|
require.Equal(t, int64(3), deleted)
|
|
|
|
rows, err = ss.RetentionPolicy().GetIdsForDeletionByTableName("Posts", 1000)
|
|
require.NoError(t, err)
|
|
// Each policy would generate it's own row
|
|
require.Equal(t, 3, len(rows))
|
|
|
|
// Clean up retention ids table
|
|
for _, row := range rows {
|
|
deleted, err = ss.Reaction().DeleteOrphanedRowsByIds(row)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), deleted)
|
|
}
|
|
})
|
|
|
|
t.Run("with preserve pinned posts true", func(t *testing.T) {
|
|
p1 := &model.Post{}
|
|
p1.ChannelId = channel.Id
|
|
p1.UserId = model.NewId()
|
|
p1.IsPinned = false
|
|
p1.Message = NewTestID()
|
|
p1.CreateAt = 1000
|
|
p1, err = ss.Post().Save(rctx, p1)
|
|
require.NoError(t, err)
|
|
|
|
p2 := &model.Post{}
|
|
p2.ChannelId = channel.Id
|
|
p2.UserId = model.NewId()
|
|
p2.IsPinned = false
|
|
p2.Message = NewTestID()
|
|
p2.CreateAt = 1000
|
|
p2, err = ss.Post().Save(rctx, p2)
|
|
require.NoError(t, err)
|
|
|
|
p3 := &model.Post{}
|
|
p3.ChannelId = channel.Id
|
|
p3.UserId = model.NewId()
|
|
p3.IsPinned = false
|
|
p3.Message = NewTestID()
|
|
p3.CreateAt = 1000
|
|
p3, err = ss.Post().Save(rctx, p3)
|
|
require.NoError(t, err)
|
|
|
|
np3 := p3.Clone()
|
|
np3.IsPinned = true
|
|
|
|
np3, err = ss.Post().Update(rctx, np3, p3)
|
|
|
|
deleted, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{
|
|
Now: 0,
|
|
GlobalPolicyEndTime: 2000,
|
|
Limit: 1000,
|
|
PreservePinnedPosts: true,
|
|
}, model.RetentionPolicyCursor{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(3), deleted)
|
|
|
|
_, err = ss.Post().Get(rctx, p1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Should have not found post 1 after purge")
|
|
|
|
_, err = ss.Post().Get(rctx, p2.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Should have not found post 2 after purge")
|
|
|
|
_, err = ss.Post().Get(rctx, p3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Should have not found post 3 before update after purge")
|
|
|
|
_, err = ss.Post().Get(rctx, np3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.NoError(t, err, "Should have found updated post 3 after purge")
|
|
|
|
rows, err = ss.RetentionPolicy().GetIdsForDeletionByTableName("Posts", 1000)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(rows))
|
|
require.Equal(t, 3, len(rows[0].Ids))
|
|
// Clean up retention ids table
|
|
deleted, err = ss.Reaction().DeleteOrphanedRowsByIds(rows[0])
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), deleted)
|
|
|
|
// Clean up pinned post
|
|
_, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{
|
|
Now: 0,
|
|
GlobalPolicyEndTime: 2000,
|
|
Limit: 1000,
|
|
PreservePinnedPosts: false,
|
|
}, model.RetentionPolicyCursor{})
|
|
require.NoError(t, err)
|
|
|
|
rows, err = ss.RetentionPolicy().GetIdsForDeletionByTableName("Posts", 1000)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(rows))
|
|
|
|
// Clean up retention ids table
|
|
_, err = ss.Reaction().DeleteOrphanedRowsByIds(rows[0])
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("with preserve pinned posts false", func(t *testing.T) {
|
|
p1 := &model.Post{}
|
|
p1.ChannelId = channel.Id
|
|
p1.UserId = model.NewId()
|
|
p1.IsPinned = false
|
|
p1.Message = NewTestID()
|
|
p1.CreateAt = 1000
|
|
p1, err = ss.Post().Save(rctx, p1)
|
|
require.NoError(t, err)
|
|
|
|
p2 := &model.Post{}
|
|
p2.ChannelId = channel.Id
|
|
p2.UserId = model.NewId()
|
|
p2.IsPinned = false
|
|
p2.Message = NewTestID()
|
|
p2.CreateAt = 1000
|
|
p2, err = ss.Post().Save(rctx, p2)
|
|
require.NoError(t, err)
|
|
|
|
p3 := &model.Post{}
|
|
p3.ChannelId = channel.Id
|
|
p3.UserId = model.NewId()
|
|
p3.IsPinned = false
|
|
p3.Message = NewTestID()
|
|
p3.CreateAt = 1000
|
|
p3, err = ss.Post().Save(rctx, p3)
|
|
require.NoError(t, err)
|
|
|
|
np3 := p3.Clone()
|
|
np3.IsPinned = true
|
|
|
|
np3, err = ss.Post().Update(rctx, np3, p3)
|
|
|
|
deleted, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(model.RetentionPolicyBatchConfigs{
|
|
Now: 0,
|
|
GlobalPolicyEndTime: 2000,
|
|
Limit: 1000,
|
|
PreservePinnedPosts: false,
|
|
}, model.RetentionPolicyCursor{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(4), deleted)
|
|
|
|
_, err = ss.Post().Get(rctx, p1.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Should have not found post 1 after purge")
|
|
|
|
_, err = ss.Post().Get(rctx, p2.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Should have not found post 2 after purge")
|
|
|
|
_, err = ss.Post().Get(rctx, p3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Should have not found post 3 before update after purge")
|
|
|
|
_, err = ss.Post().Get(rctx, np3.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
|
require.Error(t, err, "Should have not found updated post 3 after purge")
|
|
|
|
rows, err = ss.RetentionPolicy().GetIdsForDeletionByTableName("Posts", 1000)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(rows))
|
|
require.Equal(t, 4, len(rows[0].Ids))
|
|
// Clean up retention ids table
|
|
deleted, err = ss.Reaction().DeleteOrphanedRowsByIds(rows[0])
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), deleted)
|
|
})
|
|
}
|
|
|
|
func testPostStoreGetOldest(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
o0 := &model.Post{}
|
|
o0.ChannelId = channel1.Id
|
|
o0.UserId = model.NewId()
|
|
o0.Message = NewTestID()
|
|
o0.CreateAt = 3
|
|
o0, err = ss.Post().Save(rctx, o0)
|
|
require.NoError(t, err)
|
|
|
|
o1 := &model.Post{}
|
|
o1.ChannelId = o0.Id
|
|
o1.UserId = model.NewId()
|
|
o1.Message = NewTestID()
|
|
o1.CreateAt = 2
|
|
o1, err = ss.Post().Save(rctx, o1)
|
|
require.NoError(t, err)
|
|
|
|
o2 := &model.Post{}
|
|
o2.ChannelId = o1.ChannelId
|
|
o2.UserId = model.NewId()
|
|
o2.Message = NewTestID()
|
|
o2.CreateAt = 1
|
|
o2, err = ss.Post().Save(rctx, o2)
|
|
require.NoError(t, err)
|
|
|
|
r1, err := ss.Post().GetOldest()
|
|
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, o2.Id, r1.Id)
|
|
}
|
|
|
|
func testGetMaxPostSize(t *testing.T, _ request.CTX, ss store.Store) {
|
|
assert.Equal(t, model.PostMessageMaxRunesV2, ss.Post().GetMaxPostSize())
|
|
}
|
|
|
|
func testPostStoreGetParentsForExportAfter(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t1 := model.Team{}
|
|
t1.DisplayName = "Name"
|
|
t1.Name = NewTestID()
|
|
t1.Email = MakeEmail()
|
|
t1.Type = model.TeamOpen
|
|
_, err := ss.Team().Save(&t1)
|
|
require.NoError(t, err)
|
|
|
|
c1 := model.Channel{}
|
|
c1.TeamId = t1.Id
|
|
c1.DisplayName = "Channel1"
|
|
c1.Name = NewTestID()
|
|
c1.Type = model.ChannelTypeOpen
|
|
_, nErr := ss.Channel().Save(rctx, &c1, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
c2 := model.Channel{}
|
|
c2.TeamId = t1.Id
|
|
c2.DisplayName = "Channel2"
|
|
c2.Name = NewTestID()
|
|
c2.Type = model.ChannelTypeOpen
|
|
_, nErr = ss.Channel().Save(rctx, &c2, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
u1 := model.User{}
|
|
u1.Username = model.NewUsername()
|
|
u1.Email = MakeEmail()
|
|
u1.Nickname = model.NewId()
|
|
_, err = ss.User().Save(rctx, &u1)
|
|
require.NoError(t, err)
|
|
|
|
p1 := &model.Post{}
|
|
p1.ChannelId = c1.Id
|
|
p1.UserId = u1.Id
|
|
p1.Message = NewTestID()
|
|
p1.CreateAt = 1000
|
|
p1, nErr = ss.Post().Save(rctx, p1)
|
|
require.NoError(t, nErr)
|
|
|
|
p2 := &model.Post{}
|
|
p2.ChannelId = c2.Id
|
|
p2.UserId = u1.Id
|
|
p2.Message = NewTestID()
|
|
p2.CreateAt = 1000
|
|
p2, nErr = ss.Post().Save(rctx, p2)
|
|
require.NoError(t, nErr)
|
|
nErr = ss.Channel().Delete(c2.Id, model.GetMillis())
|
|
require.NoError(t, nErr)
|
|
|
|
t.Run("without archived channels", func(t *testing.T) {
|
|
posts, err := ss.Post().GetParentsForExportAfter(10000, strings.Repeat("0", 26), false)
|
|
assert.NoError(t, err)
|
|
|
|
found := false
|
|
foundArchived := false
|
|
for _, p := range posts {
|
|
if p.Id == p1.Id {
|
|
found = true
|
|
assert.Equal(t, p.Id, p1.Id)
|
|
assert.Equal(t, p.Message, p1.Message)
|
|
assert.Equal(t, p.Username, u1.Username)
|
|
assert.Equal(t, p.TeamName, t1.Name)
|
|
assert.Equal(t, p.ChannelName, c1.Name)
|
|
}
|
|
if p.Id == p2.Id {
|
|
foundArchived = true
|
|
}
|
|
}
|
|
assert.True(t, found)
|
|
assert.False(t, foundArchived, "posts from archived channel should not be returned")
|
|
})
|
|
|
|
t.Run("with archived channels", func(t *testing.T) {
|
|
posts, err := ss.Post().GetParentsForExportAfter(10000, strings.Repeat("0", 26), true)
|
|
assert.NoError(t, err)
|
|
|
|
found := false
|
|
for _, p := range posts {
|
|
if p.Id == p2.Id {
|
|
found = true
|
|
assert.Equal(t, p.Id, p2.Id)
|
|
assert.Equal(t, p.Message, p2.Message)
|
|
assert.Equal(t, p.Username, u1.Username)
|
|
assert.Equal(t, p.TeamName, t1.Name)
|
|
assert.Equal(t, p.ChannelName, c2.Name)
|
|
}
|
|
}
|
|
assert.True(t, found)
|
|
})
|
|
|
|
t.Run("with flagged post", func(t *testing.T) {
|
|
err := ss.Preference().Save(model.Preferences([]model.Preference{
|
|
{
|
|
UserId: u1.Id,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: p1.Id,
|
|
Value: "true",
|
|
},
|
|
}))
|
|
require.NoError(t, err)
|
|
|
|
posts, err := ss.Post().GetParentsForExportAfter(10000, strings.Repeat("0", 26), false)
|
|
assert.NoError(t, err)
|
|
|
|
for _, p := range posts {
|
|
if p.Id == p1.Id {
|
|
require.NotNil(t, p.FlaggedBy)
|
|
assert.Equal(t, model.StringArray([]string{u1.Username}), p.FlaggedBy)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func testPostStoreGetRepliesForExport(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t1 := model.Team{}
|
|
t1.DisplayName = "Name"
|
|
t1.Name = NewTestID()
|
|
t1.Email = MakeEmail()
|
|
t1.Type = model.TeamOpen
|
|
_, err := ss.Team().Save(&t1)
|
|
require.NoError(t, err)
|
|
|
|
c1 := model.Channel{}
|
|
c1.TeamId = t1.Id
|
|
c1.DisplayName = "Channel1"
|
|
c1.Name = NewTestID()
|
|
c1.Type = model.ChannelTypeOpen
|
|
_, nErr := ss.Channel().Save(rctx, &c1, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
u1 := model.User{}
|
|
u1.Email = MakeEmail()
|
|
u1.Nickname = model.NewId()
|
|
_, err = ss.User().Save(rctx, &u1)
|
|
require.NoError(t, err)
|
|
|
|
p1 := &model.Post{}
|
|
p1.ChannelId = c1.Id
|
|
p1.UserId = u1.Id
|
|
p1.Message = NewTestID()
|
|
p1.CreateAt = 1000
|
|
p1, nErr = ss.Post().Save(rctx, p1)
|
|
require.NoError(t, nErr)
|
|
|
|
p2 := &model.Post{}
|
|
p2.ChannelId = c1.Id
|
|
p2.UserId = u1.Id
|
|
p2.Message = NewTestID()
|
|
p2.CreateAt = 1001
|
|
p2.RootId = p1.Id
|
|
p2, nErr = ss.Post().Save(rctx, p2)
|
|
require.NoError(t, nErr)
|
|
|
|
r1, err := ss.Post().GetRepliesForExport(p1.Id)
|
|
assert.NoError(t, err)
|
|
|
|
require.Len(t, r1, 1)
|
|
|
|
reply1 := r1[0]
|
|
assert.Equal(t, reply1.Id, p2.Id)
|
|
assert.Equal(t, reply1.Message, p2.Message)
|
|
assert.Equal(t, reply1.Username, u1.Username)
|
|
|
|
// Checking whether replies by deleted user are exported
|
|
u1.DeleteAt = 1002
|
|
_, err = ss.User().Update(rctx, &u1, false)
|
|
require.NoError(t, err)
|
|
|
|
r1, err = ss.Post().GetRepliesForExport(p1.Id)
|
|
assert.NoError(t, err)
|
|
|
|
require.Len(t, r1, 1)
|
|
|
|
reply1 = r1[0]
|
|
assert.Equal(t, reply1.Id, p2.Id)
|
|
assert.Equal(t, reply1.Message, p2.Message)
|
|
assert.Equal(t, reply1.Username, u1.Username)
|
|
}
|
|
|
|
func testPostStoreGetDirectPostParentsForExportAfter(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
teamID := model.NewId()
|
|
|
|
o1 := model.Channel{}
|
|
o1.TeamId = teamID
|
|
o1.DisplayName = "Name"
|
|
o1.Name = model.GetDMNameFromIds(NewTestID(), NewTestID())
|
|
o1.Type = model.ChannelTypeDirect
|
|
|
|
u1 := &model.User{}
|
|
u1.Email = MakeEmail()
|
|
u1.Nickname = model.NewId()
|
|
_, err := ss.User().Save(rctx, u1)
|
|
require.NoError(t, err)
|
|
_, nErr := ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
u2 := &model.User{}
|
|
u2.Email = MakeEmail()
|
|
u2.Nickname = model.NewId()
|
|
_, err = ss.User().Save(rctx, u2)
|
|
require.NoError(t, err)
|
|
_, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
m1 := model.ChannelMember{}
|
|
m1.ChannelId = o1.Id
|
|
m1.UserId = u1.Id
|
|
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
|
|
m2 := model.ChannelMember{}
|
|
m2.ChannelId = o1.Id
|
|
m2.UserId = u2.Id
|
|
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
|
|
ss.Channel().SaveDirectChannel(rctx, &o1, &m1, &m2)
|
|
|
|
p1 := &model.Post{}
|
|
p1.ChannelId = o1.Id
|
|
p1.UserId = u1.Id
|
|
p1.Message = NewTestID()
|
|
p1.CreateAt = 1000
|
|
p1, nErr = ss.Post().Save(rctx, p1)
|
|
require.NoError(t, nErr)
|
|
|
|
r1, nErr := ss.Post().GetDirectPostParentsForExportAfter(10000, strings.Repeat("0", 26), false)
|
|
assert.NoError(t, nErr)
|
|
|
|
assert.Equal(t, p1.Message, r1[0].Message)
|
|
|
|
// Manually truncate Channels table until testlib can handle cleanups
|
|
s.GetMaster().Exec("TRUNCATE Channels")
|
|
}
|
|
|
|
func testPostStoreGetDirectPostParentsForExportAfterDeleted(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
teamID := model.NewId()
|
|
|
|
o1 := model.Channel{}
|
|
o1.TeamId = teamID
|
|
o1.DisplayName = "Name"
|
|
o1.Name = model.GetDMNameFromIds(NewTestID(), NewTestID())
|
|
o1.Type = model.ChannelTypeDirect
|
|
|
|
u1 := &model.User{}
|
|
u1.DeleteAt = 1
|
|
u1.Email = MakeEmail()
|
|
u1.Nickname = model.NewId()
|
|
_, err := ss.User().Save(rctx, u1)
|
|
require.NoError(t, err)
|
|
_, nErr := ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
u2 := &model.User{}
|
|
u2.DeleteAt = 1
|
|
u2.Email = MakeEmail()
|
|
u2.Nickname = model.NewId()
|
|
_, err = ss.User().Save(rctx, u2)
|
|
require.NoError(t, err)
|
|
_, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
m1 := model.ChannelMember{}
|
|
m1.ChannelId = o1.Id
|
|
m1.UserId = u1.Id
|
|
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
|
|
m2 := model.ChannelMember{}
|
|
m2.ChannelId = o1.Id
|
|
m2.UserId = u2.Id
|
|
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
|
|
ss.Channel().SaveDirectChannel(rctx, &o1, &m1, &m2)
|
|
|
|
o1.DeleteAt = 1
|
|
nErr = ss.Channel().SetDeleteAt(o1.Id, 1, 1)
|
|
assert.NoError(t, nErr)
|
|
|
|
p1 := &model.Post{}
|
|
p1.ChannelId = o1.Id
|
|
p1.UserId = u1.Id
|
|
p1.Message = NewTestID()
|
|
p1.CreateAt = 1000
|
|
_, nErr = ss.Post().Save(rctx, p1)
|
|
require.NoError(t, nErr)
|
|
|
|
r1, nErr := ss.Post().GetDirectPostParentsForExportAfter(10000, strings.Repeat("0", 26), false)
|
|
assert.NoError(t, nErr)
|
|
assert.Equal(t, 0, len(r1))
|
|
|
|
r1, nErr = ss.Post().GetDirectPostParentsForExportAfter(10000, strings.Repeat("0", 26), true)
|
|
assert.NoError(t, nErr)
|
|
assert.Equal(t, 1, len(r1))
|
|
|
|
// Manually truncate Channels table until testlib can handle cleanups
|
|
s.GetMaster().Exec("TRUNCATE Channels")
|
|
}
|
|
|
|
func testPostStoreGetDirectPostParentsForExportAfterBatched(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
teamID := model.NewId()
|
|
|
|
o1 := model.Channel{}
|
|
o1.TeamId = teamID
|
|
o1.DisplayName = "Name"
|
|
o1.Name = model.GetDMNameFromIds(NewTestID(), NewTestID())
|
|
o1.Type = model.ChannelTypeDirect
|
|
|
|
var postIds []string
|
|
for range 150 {
|
|
u1 := &model.User{}
|
|
u1.Email = MakeEmail()
|
|
u1.Nickname = model.NewId()
|
|
_, err := ss.User().Save(rctx, u1)
|
|
require.NoError(t, err)
|
|
_, nErr := ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
u2 := &model.User{}
|
|
u2.Email = MakeEmail()
|
|
u2.Nickname = model.NewId()
|
|
_, err = ss.User().Save(rctx, u2)
|
|
require.NoError(t, err)
|
|
_, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}, -1)
|
|
require.NoError(t, nErr)
|
|
|
|
m1 := model.ChannelMember{}
|
|
m1.ChannelId = o1.Id
|
|
m1.UserId = u1.Id
|
|
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
|
|
m2 := model.ChannelMember{}
|
|
m2.ChannelId = o1.Id
|
|
m2.UserId = u2.Id
|
|
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
|
|
ss.Channel().SaveDirectChannel(rctx, &o1, &m1, &m2)
|
|
|
|
p1 := &model.Post{}
|
|
p1.ChannelId = o1.Id
|
|
p1.UserId = u1.Id
|
|
p1.Message = NewTestID()
|
|
p1.CreateAt = 1000
|
|
p1, nErr = ss.Post().Save(rctx, p1)
|
|
require.NoError(t, nErr)
|
|
postIds = append(postIds, p1.Id)
|
|
}
|
|
slices.Sort(postIds)
|
|
|
|
// Get all posts
|
|
r1, err := ss.Post().GetDirectPostParentsForExportAfter(10000, strings.Repeat("0", 26), false)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(postIds), len(r1))
|
|
var exportedPostIds []string
|
|
for i := range r1 {
|
|
exportedPostIds = append(exportedPostIds, r1[i].Id)
|
|
}
|
|
slices.Sort(exportedPostIds)
|
|
assert.ElementsMatch(t, postIds, exportedPostIds)
|
|
|
|
// Get 100
|
|
r1, err = ss.Post().GetDirectPostParentsForExportAfter(100, strings.Repeat("0", 26), false)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 100, len(r1))
|
|
exportedPostIds = []string{}
|
|
for i := range r1 {
|
|
exportedPostIds = append(exportedPostIds, r1[i].Id)
|
|
}
|
|
slices.Sort(exportedPostIds)
|
|
assert.ElementsMatch(t, postIds[:100], exportedPostIds)
|
|
|
|
// Manually truncate Channels table until testlib can handle cleanups
|
|
s.GetMaster().Exec("TRUNCATE Channels")
|
|
}
|
|
|
|
func testHasAutoResponsePostByUserSince(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t.Run("should return posts created after the given time", func(t *testing.T) {
|
|
teamID := model.NewId()
|
|
channel1, err := ss.Channel().Save(rctx, &model.Channel{
|
|
TeamId: teamID,
|
|
DisplayName: "DisplayName1",
|
|
Name: "channel" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
}, -1)
|
|
require.NoError(t, err)
|
|
|
|
channelID := channel1.Id
|
|
userID := model.NewId()
|
|
|
|
_, err = ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
// We need to sleep because SendAutoResponseIfNecessary
|
|
// runs in a goroutine.
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post2, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "message",
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
post3, err := ss.Post().Save(rctx, &model.Post{
|
|
ChannelId: channelID,
|
|
UserId: userID,
|
|
Message: "auto response message",
|
|
Type: model.PostTypeAutoResponder,
|
|
})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
exists, err := ss.Post().HasAutoResponsePostByUserSince(model.GetPostsSinceOptions{ChannelId: channelID, Time: post2.CreateAt}, userID)
|
|
require.NoError(t, err)
|
|
assert.True(t, exists)
|
|
|
|
err = ss.Post().Delete(rctx, post3.Id, time.Now().Unix(), userID)
|
|
require.NoError(t, err)
|
|
|
|
exists, err = ss.Post().HasAutoResponsePostByUserSince(model.GetPostsSinceOptions{ChannelId: channelID, Time: post2.CreateAt}, userID)
|
|
require.NoError(t, err)
|
|
assert.False(t, exists)
|
|
})
|
|
}
|
|
|
|
func testGetPostsSinceUpdateForSync(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
// create some posts.
|
|
channelID := model.NewId()
|
|
remoteID := model.NewPointer(model.NewId())
|
|
first := model.GetMillis()
|
|
|
|
data := []*model.Post{
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 0"},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 1"},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 2"},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 3", RemoteId: remoteID},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 4", RemoteId: remoteID},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 5", RemoteId: remoteID},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 6", RemoteId: remoteID},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 7"},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 8", DeleteAt: model.GetMillis()},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 9", DeleteAt: model.GetMillis()},
|
|
}
|
|
|
|
for i, p := range data {
|
|
p.UpdateAt = first + (int64(i) * 300000)
|
|
if p.RemoteId == nil {
|
|
p.RemoteId = model.NewPointer(model.NewId())
|
|
}
|
|
_, err := ss.Post().Save(rctx, p)
|
|
require.NoError(t, err, "couldn't save post")
|
|
}
|
|
|
|
t.Run("Invalid channel id", func(t *testing.T) {
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: model.NewId(),
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts, cursorOut, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
|
require.NoError(t, err)
|
|
require.Empty(t, posts, "should return zero posts")
|
|
require.Equal(t, cursor, cursorOut)
|
|
})
|
|
|
|
t.Run("Get by channel, exclude remotes, exclude deleted", func(t *testing.T) {
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
ExcludeRemoteId: *remoteID,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
|
require.NoError(t, err)
|
|
|
|
require.ElementsMatch(t, getPostIds(data[0:3], data[7]), getPostIds(posts))
|
|
})
|
|
|
|
t.Run("Include deleted", func(t *testing.T) {
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
IncludeDeleted: true,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
|
require.NoError(t, err)
|
|
|
|
require.ElementsMatch(t, getPostIds(data), getPostIds(posts))
|
|
})
|
|
|
|
t.Run("Limit and cursor", func(t *testing.T) {
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts1, cursor, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts1, 5, "should get 5 posts")
|
|
|
|
posts2, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts2, 3, "should get 3 posts")
|
|
|
|
require.ElementsMatch(t, getPostIds(data[0:8]), getPostIds(posts1, posts2...))
|
|
})
|
|
|
|
t.Run("UpdateAt collisions", func(t *testing.T) {
|
|
// this test requires all the UpdateAt timestamps to be the same.
|
|
result, err := s.GetMaster().Exec("UPDATE Posts SET UpdateAt = ?", model.GetMillis())
|
|
require.NoError(t, err)
|
|
rows, err := result.RowsAffected()
|
|
require.NoError(t, err)
|
|
require.Greater(t, rows, int64(0))
|
|
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts1, cursor, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts1, 5, "should get 5 posts")
|
|
|
|
posts2, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts2, 3, "should get 3 posts")
|
|
|
|
require.ElementsMatch(t, getPostIds(data[0:8]), getPostIds(posts1, posts2...))
|
|
})
|
|
}
|
|
|
|
func testGetPostsSinceCreateForSync(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
// create some posts.
|
|
channelID := model.NewId()
|
|
remoteID := model.NewPointer(model.NewId())
|
|
first := model.GetMillis()
|
|
|
|
data := []*model.Post{
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 0"},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 1"},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 2"},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 3", RemoteId: remoteID},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 4", RemoteId: remoteID},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 5", RemoteId: remoteID},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 6", RemoteId: remoteID},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 7"},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 8", DeleteAt: model.GetMillis()},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "test post 9", DeleteAt: model.GetMillis()},
|
|
}
|
|
|
|
for i, p := range data {
|
|
p.CreateAt = first + (int64(i) * 300000)
|
|
if p.RemoteId == nil {
|
|
p.RemoteId = model.NewPointer(model.NewId())
|
|
}
|
|
_, err := ss.Post().Save(rctx, p)
|
|
require.NoError(t, err, "couldn't save post")
|
|
}
|
|
|
|
t.Run("Invalid channel id", func(t *testing.T) {
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: model.NewId(),
|
|
SinceCreateAt: true,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts, cursorOut, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
|
require.NoError(t, err)
|
|
require.Empty(t, posts, "should return zero posts")
|
|
require.Equal(t, cursor, cursorOut)
|
|
})
|
|
|
|
t.Run("Get by channel, exclude remotes, exclude deleted", func(t *testing.T) {
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
ExcludeRemoteId: *remoteID,
|
|
SinceCreateAt: true,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
|
require.NoError(t, err)
|
|
|
|
require.ElementsMatch(t, getPostIds(data[0:3], data[7]), getPostIds(posts))
|
|
})
|
|
|
|
t.Run("Include deleted", func(t *testing.T) {
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
IncludeDeleted: true,
|
|
SinceCreateAt: true,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
|
require.NoError(t, err)
|
|
|
|
require.ElementsMatch(t, getPostIds(data), getPostIds(posts))
|
|
})
|
|
|
|
t.Run("Limit and cursor", func(t *testing.T) {
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
SinceCreateAt: true,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts1, cursor, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts1, 5, "should get 5 posts")
|
|
|
|
posts2, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts2, 3, "should get 3 posts")
|
|
|
|
require.ElementsMatch(t, getPostIds(data[0:8]), getPostIds(posts1, posts2...))
|
|
})
|
|
|
|
t.Run("CreateAt collisions", func(t *testing.T) {
|
|
// this test requires all the CreateAt timestamps to be the same.
|
|
result, err := s.GetMaster().Exec("UPDATE Posts SET CreateAt = ?", model.GetMillis())
|
|
require.NoError(t, err)
|
|
rows, err := result.RowsAffected()
|
|
require.NoError(t, err)
|
|
require.Greater(t, rows, int64(0))
|
|
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts1, cursor, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts1, 5, "should get 5 posts")
|
|
|
|
posts2, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 5)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts2, 3, "should get 3 posts")
|
|
|
|
require.ElementsMatch(t, getPostIds(data[0:8]), getPostIds(posts1, posts2...))
|
|
})
|
|
}
|
|
|
|
func testSetPostReminder(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
// Basic
|
|
userID := NewTestID()
|
|
|
|
p1 := &model.Post{
|
|
UserId: userID,
|
|
ChannelId: NewTestID(),
|
|
Message: "hi there",
|
|
Type: model.PostTypeDefault,
|
|
}
|
|
p1, err := ss.Post().Save(rctx, p1)
|
|
require.NoError(t, err)
|
|
|
|
reminder := &model.PostReminder{
|
|
TargetTime: 1234,
|
|
PostId: p1.Id,
|
|
UserId: userID,
|
|
}
|
|
|
|
require.NoError(t, ss.Post().SetPostReminder(reminder))
|
|
|
|
out := model.PostReminder{}
|
|
require.NoError(t, s.GetMaster().Get(&out, `SELECT PostId, UserId, TargetTime FROM PostReminders WHERE PostId=? AND UserId=?`, reminder.PostId, reminder.UserId))
|
|
assert.Equal(t, reminder, &out)
|
|
|
|
reminder.PostId = "notfound"
|
|
err = ss.Post().SetPostReminder(reminder)
|
|
var nfErr *store.ErrNotFound
|
|
require.True(t, errors.As(err, &nfErr))
|
|
|
|
// Upsert
|
|
reminder = &model.PostReminder{
|
|
TargetTime: 12345,
|
|
PostId: p1.Id,
|
|
UserId: userID,
|
|
}
|
|
|
|
require.NoError(t, ss.Post().SetPostReminder(reminder))
|
|
require.NoError(t, s.GetMaster().Get(&out, `SELECT PostId, UserId, TargetTime FROM PostReminders WHERE PostId=? AND UserId=?`, reminder.PostId, reminder.UserId))
|
|
assert.Equal(t, reminder, &out)
|
|
}
|
|
|
|
func testGetPostReminders(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
times := []int64{100, 101, 102}
|
|
for _, tt := range times {
|
|
userID := NewTestID()
|
|
|
|
p1 := &model.Post{
|
|
UserId: userID,
|
|
ChannelId: NewTestID(),
|
|
Message: "hi there",
|
|
Type: model.PostTypeDefault,
|
|
}
|
|
p1, err := ss.Post().Save(rctx, p1)
|
|
require.NoError(t, err)
|
|
|
|
reminder := &model.PostReminder{
|
|
TargetTime: tt,
|
|
PostId: p1.Id,
|
|
UserId: userID,
|
|
}
|
|
|
|
require.NoError(t, ss.Post().SetPostReminder(reminder))
|
|
}
|
|
|
|
reminders, err := ss.Post().GetPostReminders(101)
|
|
require.NoError(t, err)
|
|
require.Len(t, reminders, 2)
|
|
|
|
// assert one reminder is left
|
|
reminders, err = ss.Post().GetPostReminders(102)
|
|
require.NoError(t, err)
|
|
require.Len(t, reminders, 1)
|
|
|
|
// assert everything is deleted.
|
|
reminders, err = ss.Post().GetPostReminders(103)
|
|
require.NoError(t, err)
|
|
require.Len(t, reminders, 0)
|
|
}
|
|
|
|
func testGetPostReminderMetadata(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
team := &model.Team{
|
|
Name: "teamname",
|
|
DisplayName: "display",
|
|
Type: model.TeamOpen,
|
|
}
|
|
team, err := ss.Team().Save(team)
|
|
require.NoError(t, err)
|
|
|
|
ch := &model.Channel{
|
|
TeamId: team.Id,
|
|
DisplayName: "channeldisplay",
|
|
Name: NewTestID(),
|
|
Type: model.ChannelTypeOpen,
|
|
}
|
|
ch, err = ss.Channel().Save(rctx, ch, -1)
|
|
require.NoError(t, err)
|
|
|
|
ch2 := &model.Channel{
|
|
TeamId: "",
|
|
DisplayName: "GM_display",
|
|
Name: NewTestID(),
|
|
Type: model.ChannelTypeGroup,
|
|
}
|
|
ch2, err = ss.Channel().Save(rctx, ch2, -1)
|
|
require.NoError(t, err)
|
|
|
|
u1 := &model.User{
|
|
Email: MakeEmail(),
|
|
Username: model.NewUsername(),
|
|
Locale: "es",
|
|
}
|
|
|
|
u1, err = ss.User().Save(rctx, u1)
|
|
require.NoError(t, err)
|
|
|
|
p1 := &model.Post{
|
|
UserId: u1.Id,
|
|
ChannelId: ch.Id,
|
|
Message: "hi there",
|
|
Type: model.PostTypeDefault,
|
|
}
|
|
p1, err = ss.Post().Save(rctx, p1)
|
|
require.NoError(t, err)
|
|
|
|
p2 := &model.Post{
|
|
UserId: u1.Id,
|
|
ChannelId: ch2.Id,
|
|
Message: "hi there 2",
|
|
Type: model.PostTypeDefault,
|
|
}
|
|
p2, err = ss.Post().Save(rctx, p2)
|
|
require.NoError(t, err)
|
|
|
|
meta, err := ss.Post().GetPostReminderMetadata(p1.Id)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, meta.ChannelID, ch.Id)
|
|
assert.Equal(t, meta.TeamName, team.Name)
|
|
assert.Equal(t, meta.Username, u1.Username)
|
|
assert.Equal(t, meta.UserLocale, u1.Locale)
|
|
|
|
meta, err = ss.Post().GetPostReminderMetadata(p2.Id)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, meta.ChannelID, ch2.Id)
|
|
assert.Equal(t, meta.TeamName, "")
|
|
assert.Equal(t, meta.Username, u1.Username)
|
|
assert.Equal(t, meta.UserLocale, u1.Locale)
|
|
}
|
|
|
|
func getPostIds(posts []*model.Post, morePosts ...*model.Post) []string {
|
|
ids := make([]string, 0, len(posts)+len(morePosts))
|
|
for _, p := range posts {
|
|
ids = append(ids, p.Id)
|
|
}
|
|
for _, p := range morePosts {
|
|
ids = append(ids, p.Id)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func testGetNthRecentPostTime(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t.Skip("https://mattermost.atlassian.net/browse/MM-64438")
|
|
|
|
_, err := ss.Post().GetNthRecentPostTime(0)
|
|
assert.Error(t, err)
|
|
_, err = ss.Post().GetNthRecentPostTime(-1)
|
|
assert.Error(t, err)
|
|
|
|
diff := int64(10000)
|
|
now := utils.MillisFromTime(time.Now()) + diff
|
|
|
|
p1 := &model.Post{}
|
|
p1.ChannelId = model.NewId()
|
|
p1.UserId = model.NewId()
|
|
p1.Message = "test"
|
|
p1.CreateAt = now
|
|
p1, err = ss.Post().Save(rctx, p1)
|
|
require.NoError(t, err)
|
|
|
|
p2 := &model.Post{}
|
|
p2.ChannelId = p1.ChannelId
|
|
p2.UserId = p1.UserId
|
|
p2.Message = p1.Message
|
|
now = now + diff
|
|
p2.CreateAt = now
|
|
p2, err = ss.Post().Save(rctx, p2)
|
|
require.NoError(t, err)
|
|
|
|
bot1 := &model.Bot{
|
|
Username: "username",
|
|
Description: "a bot",
|
|
OwnerId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
}
|
|
_, err = ss.Bot().Save(bot1)
|
|
require.NoError(t, err)
|
|
|
|
b1 := &model.Post{}
|
|
b1.Message = "bot test"
|
|
b1.ChannelId = p1.ChannelId
|
|
b1.UserId = bot1.UserId
|
|
now = now + diff
|
|
b1.CreateAt = now
|
|
_, err = ss.Post().Save(rctx, b1)
|
|
require.NoError(t, err)
|
|
|
|
p3 := &model.Post{}
|
|
p3.ChannelId = p1.ChannelId
|
|
p3.UserId = p1.UserId
|
|
p3.Message = p1.Message
|
|
now = now + diff
|
|
p3.CreateAt = now
|
|
p3, err = ss.Post().Save(rctx, p3)
|
|
require.NoError(t, err)
|
|
|
|
s1 := &model.Post{}
|
|
s1.Type = model.PostTypeJoinChannel
|
|
s1.ChannelId = p1.ChannelId
|
|
s1.UserId = model.NewId()
|
|
s1.Message = "system_join_channel message"
|
|
now = now + diff
|
|
s1.CreateAt = now
|
|
_, err = ss.Post().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
p4 := &model.Post{}
|
|
p4.ChannelId = p1.ChannelId
|
|
p4.UserId = p1.UserId
|
|
p4.Message = p1.Message
|
|
now = now + diff
|
|
p4.CreateAt = now
|
|
p4, err = ss.Post().Save(rctx, p4)
|
|
require.NoError(t, err)
|
|
|
|
r, err := ss.Post().GetNthRecentPostTime(1)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, p4.CreateAt, r)
|
|
|
|
// Skip system post
|
|
r, err = ss.Post().GetNthRecentPostTime(2)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, p3.CreateAt, r)
|
|
|
|
// Skip system & bot post
|
|
r, err = ss.Post().GetNthRecentPostTime(3)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, p2.CreateAt, r)
|
|
|
|
r, err = ss.Post().GetNthRecentPostTime(4)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, p1.CreateAt, r)
|
|
|
|
_, err = ss.Post().GetNthRecentPostTime(10000)
|
|
assert.Error(t, err)
|
|
assert.IsType(t, &store.ErrNotFound{}, err)
|
|
}
|
|
|
|
func testGetEditHistoryForPost(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
t.Run("should return edit history for post", func(t *testing.T) {
|
|
// create a post
|
|
post := &model.Post{
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
Message: "test",
|
|
}
|
|
originalPost, err := ss.Post().Save(rctx, post)
|
|
require.NoError(t, err)
|
|
// create an edit
|
|
updatedPost := originalPost.Clone()
|
|
updatedPost.Message = "test edited"
|
|
savedUpdatedPost, err := ss.Post().Update(rctx, updatedPost, originalPost)
|
|
require.NoError(t, err)
|
|
// get edit history
|
|
edits, err := ss.Post().GetEditHistoryForPost(savedUpdatedPost.Id)
|
|
require.NoError(t, err)
|
|
require.Len(t, edits, 1)
|
|
require.Equal(t, originalPost.Id, edits[0].Id)
|
|
require.Equal(t, originalPost.UserId, edits[0].UserId)
|
|
require.Equal(t, originalPost.Message, edits[0].Message)
|
|
})
|
|
|
|
t.Run("should return error for not edited posts", func(t *testing.T) {
|
|
// create a post
|
|
post := &model.Post{
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
Message: "test",
|
|
}
|
|
originalPost, err := ss.Post().Save(rctx, post)
|
|
require.NoError(t, err)
|
|
// get edit history
|
|
_, err = ss.Post().GetEditHistoryForPost(originalPost.Id)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("should return error for non-existent post", func(t *testing.T) {
|
|
// get edit history
|
|
_, err := ss.Post().GetEditHistoryForPost("non-existent")
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("should return error for deleted post", func(t *testing.T) {
|
|
// create a post
|
|
post := &model.Post{
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
Message: "test",
|
|
}
|
|
originalPost, err := ss.Post().Save(rctx, post)
|
|
require.NoError(t, err)
|
|
// delete post
|
|
err = ss.Post().Delete(rctx, post.Id, 100, post.UserId)
|
|
require.NoError(t, err)
|
|
// get edit history
|
|
_, err = ss.Post().GetEditHistoryForPost(originalPost.Id)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("should return error for deleted edit", func(t *testing.T) {
|
|
// create a post
|
|
post := &model.Post{
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
Message: "test",
|
|
}
|
|
originalPost, err := ss.Post().Save(rctx, post)
|
|
require.NoError(t, err)
|
|
// create an edit
|
|
updatedPost := originalPost.Clone()
|
|
updatedPost.Message = "test edited"
|
|
savedUpdatedPost, err := ss.Post().Update(rctx, updatedPost, originalPost)
|
|
require.NoError(t, err)
|
|
// delete edit
|
|
err = ss.Post().Delete(rctx, savedUpdatedPost.Id, 100, savedUpdatedPost.UserId)
|
|
require.NoError(t, err)
|
|
// get edit history
|
|
_, err = ss.Post().GetEditHistoryForPost(savedUpdatedPost.Id)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
// testGetPostsSinceForSyncExcludeMetadata tests the ExcludeChannelMetadataSystemPosts option
|
|
// in the GetPostsSinceForSync function to verify that database-level filtering works correctly
|
|
func testGetPostsSinceForSyncExcludeMetadata(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
// Create a channel
|
|
channelID := model.NewId()
|
|
|
|
// Create test posts - mix of regular posts and channel metadata system posts
|
|
first := model.GetMillis()
|
|
|
|
data := []*model.Post{
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "regular post 1", Type: model.PostTypeDefault},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "changed header", Type: model.PostTypeHeaderChange},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "regular post 2", Type: model.PostTypeDefault},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "changed display name", Type: model.PostTypeDisplaynameChange},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "regular post 3", Type: model.PostTypeDefault},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "changed purpose", Type: model.PostTypePurposeChange},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "regular post 4", Type: model.PostTypeDefault},
|
|
}
|
|
|
|
// Save posts
|
|
for i, p := range data {
|
|
p.UpdateAt = first + (int64(i) * 300000)
|
|
p.CreateAt = first + (int64(i) * 300000)
|
|
p.RemoteId = model.NewPointer(model.NewId())
|
|
_, err := ss.Post().Save(rctx, p)
|
|
require.NoError(t, err, "couldn't save post")
|
|
}
|
|
|
|
t.Run("ExcludeChannelMetadataSystemPosts=true should filter out metadata posts", func(t *testing.T) {
|
|
// Set options with ExcludeChannelMetadataSystemPosts = true
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
ExcludeChannelMetadataSystemPosts: true,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
|
require.NoError(t, err)
|
|
|
|
// Verify only regular posts are returned
|
|
require.Len(t, posts, 4, "should return only 4 regular posts")
|
|
|
|
// Check that only default post types are returned
|
|
for _, post := range posts {
|
|
require.Equal(t, model.PostTypeDefault, post.Type, "only default posts should be returned")
|
|
}
|
|
|
|
// Verify we have the expected post IDs (only regular posts)
|
|
expectedIDs := []string{
|
|
data[0].Id, // regular post 1
|
|
data[2].Id, // regular post 2
|
|
data[4].Id, // regular post 3
|
|
data[6].Id, // regular post 4
|
|
}
|
|
|
|
postIDs := make([]string, 0, len(posts))
|
|
for _, p := range posts {
|
|
postIDs = append(postIDs, p.Id)
|
|
}
|
|
|
|
require.ElementsMatch(t, expectedIDs, postIDs, "returned posts should only be regular posts")
|
|
})
|
|
|
|
t.Run("ExcludeChannelMetadataSystemPosts=false should include all posts", func(t *testing.T) {
|
|
// Set options with ExcludeChannelMetadataSystemPosts = false
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
ExcludeChannelMetadataSystemPosts: false,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
|
require.NoError(t, err)
|
|
|
|
// Verify all posts are returned
|
|
require.Len(t, posts, 7, "should return all 7 posts when not excluding metadata posts")
|
|
|
|
// Verify all post types are included by counting each type
|
|
postTypeCount := make(map[string]int)
|
|
for _, post := range posts {
|
|
postTypeCount[post.Type]++
|
|
}
|
|
|
|
require.Equal(t, 4, postTypeCount[model.PostTypeDefault], "should have 4 regular posts")
|
|
require.Equal(t, 1, postTypeCount[model.PostTypeHeaderChange], "should have 1 header change post")
|
|
require.Equal(t, 1, postTypeCount[model.PostTypeDisplaynameChange], "should have 1 display name change post")
|
|
require.Equal(t, 1, postTypeCount[model.PostTypePurposeChange], "should have 1 purpose change post")
|
|
})
|
|
}
|
|
|
|
func testGetPostsSinceForSyncExcludedPostTypes(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
channelID := model.NewId()
|
|
first := model.GetMillis()
|
|
|
|
data := []*model.Post{
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "regular post", Type: model.PostTypeDefault},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "card post", Type: model.PostTypeCard},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "another regular", Type: model.PostTypeDefault},
|
|
{Id: model.NewId(), ChannelId: channelID, UserId: model.NewId(), Message: "join post", Type: model.PostTypeJoinChannel},
|
|
}
|
|
|
|
for i, p := range data {
|
|
p.UpdateAt = first + (int64(i) * 300000)
|
|
p.CreateAt = first + (int64(i) * 300000)
|
|
p.RemoteId = model.NewPointer(model.NewId())
|
|
_, err := ss.Post().Save(rctx, p)
|
|
require.NoError(t, err, "couldn't save post")
|
|
}
|
|
|
|
t.Run("ExcludedPostTypes filters out specified types", func(t *testing.T) {
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
ExcludedPostTypes: []string{model.PostTypeCard},
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts, 3, "should exclude the card post")
|
|
|
|
for _, post := range posts {
|
|
require.NotEqual(t, model.PostTypeCard, post.Type)
|
|
}
|
|
})
|
|
|
|
t.Run("ExcludedPostTypes with multiple types", func(t *testing.T) {
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
ExcludedPostTypes: []string{model.PostTypeCard, model.PostTypeJoinChannel},
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts, 2, "should exclude card and join posts")
|
|
|
|
for _, post := range posts {
|
|
require.Equal(t, model.PostTypeDefault, post.Type)
|
|
}
|
|
})
|
|
|
|
t.Run("empty ExcludedPostTypes returns all posts", func(t *testing.T) {
|
|
opt := model.GetPostsSinceForSyncOptions{
|
|
ChannelId: channelID,
|
|
}
|
|
cursor := model.GetPostsSinceForSyncCursor{}
|
|
posts, _, err := ss.Post().GetPostsSinceForSync(opt, cursor, 100)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts, 4, "should return all posts when no exclusions")
|
|
})
|
|
}
|
|
|
|
// buildReportPostQueryParams is a test helper to build ReportPostQueryParams for store tests.
|
|
// Store tests focus on SQL query logic, not parameter resolution/validation (which happens in API layer).
|
|
func buildReportPostQueryParams(channelId, timeField, sortDirection string, perPage int, includeDeleted, excludeSystemPosts bool) model.ReportPostQueryParams {
|
|
// Set defaults
|
|
if timeField == "" {
|
|
timeField = model.ReportingTimeFieldCreateAt
|
|
}
|
|
if sortDirection == "" {
|
|
sortDirection = model.ReportingSortDirectionAsc
|
|
}
|
|
if perPage == 0 {
|
|
perPage = 100
|
|
}
|
|
|
|
// Set initial cursor position based on sort direction
|
|
cursorTime := int64(0) // ASC: start from beginning
|
|
if sortDirection == model.ReportingSortDirectionDesc {
|
|
cursorTime = int64(^uint64(0) >> 1) // MaxInt64
|
|
}
|
|
|
|
return model.ReportPostQueryParams{
|
|
ChannelId: channelId,
|
|
CursorTime: cursorTime,
|
|
CursorId: "",
|
|
TimeField: timeField,
|
|
SortDirection: sortDirection,
|
|
IncludeDeleted: includeDeleted,
|
|
ExcludeSystemPosts: excludeSystemPosts,
|
|
PerPage: perPage,
|
|
}
|
|
}
|
|
|
|
func testGetPostsForReporting(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
channelID := model.NewId()
|
|
first := model.GetMillis()
|
|
|
|
// Create test posts with predictable timestamps
|
|
data := []*model.Post{
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "post 0"},
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "post 1"},
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "post 2"},
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "post 3"},
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "post 4"},
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "post 5"},
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "post 6"},
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "post 7"},
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "post 8", DeleteAt: model.GetMillis()},
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "post 9", DeleteAt: model.GetMillis()},
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "header change", Type: model.PostTypeHeaderChange},
|
|
{ChannelId: channelID, UserId: model.NewId(), Message: "name change", Type: model.PostTypeDisplaynameChange},
|
|
}
|
|
|
|
for i, p := range data {
|
|
p.CreateAt = first + (int64(i) * 300000)
|
|
p.UpdateAt = first + (int64(i) * 300000)
|
|
saved, err := ss.Post().Save(rctx, p)
|
|
require.NoError(t, err, "couldn't save post")
|
|
data[i] = saved // Update with saved post (includes generated ID)
|
|
}
|
|
|
|
t.Run("Invalid channel id", func(t *testing.T) {
|
|
queryParams := buildReportPostQueryParams(model.NewId(), "", "", 0, false, false)
|
|
result, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err)
|
|
require.Empty(t, result.Posts, "should return zero posts")
|
|
require.Nil(t, result.NextCursor, "should not have next cursor")
|
|
})
|
|
|
|
t.Run("empty cursor is valid for first page", func(t *testing.T) {
|
|
queryParams := buildReportPostQueryParams(channelID, model.ReportingTimeFieldCreateAt, model.ReportingSortDirectionAsc, 100, false, false)
|
|
result, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err, "empty cursor should be valid")
|
|
require.NotNil(t, result)
|
|
require.Greater(t, len(result.Posts), 0, "should return posts")
|
|
})
|
|
|
|
t.Run("Basic cursor pagination with create_at ASC", func(t *testing.T) {
|
|
// First page
|
|
queryParams := buildReportPostQueryParams(channelID, model.ReportingTimeFieldCreateAt, model.ReportingSortDirectionAsc, 5, false, false)
|
|
result1, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err)
|
|
require.Len(t, result1.Posts, 5, "should get 5 posts")
|
|
require.NotNil(t, result1.NextCursor, "should have next cursor")
|
|
|
|
// Second page - decode cursor from first page
|
|
queryParams2, appErr := model.DecodeReportPostCursorV1(result1.NextCursor.Cursor)
|
|
require.Nil(t, appErr)
|
|
queryParams2.PerPage = 5
|
|
result2, err := ss.Post().GetPostsForReporting(rctx, *queryParams2)
|
|
require.NoError(t, err)
|
|
require.Len(t, result2.Posts, 5, "should get 5 posts")
|
|
require.Nil(t, result2.NextCursor, "should not have next cursor (10 non-deleted posts total: 0-7,10-11)")
|
|
})
|
|
|
|
t.Run("DESC sort order", func(t *testing.T) {
|
|
queryParams := buildReportPostQueryParams(channelID, model.ReportingTimeFieldCreateAt, model.ReportingSortDirectionDesc, 3, false, false)
|
|
result, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Posts, 3, "should get 3 posts")
|
|
require.NotNil(t, result.NextCursor, "should have next cursor")
|
|
|
|
// Verify we got the right count
|
|
require.Len(t, result.Posts, 3, "should have 3 posts")
|
|
})
|
|
|
|
t.Run("Include deleted posts", func(t *testing.T) {
|
|
queryParams := buildReportPostQueryParams(channelID, model.ReportingTimeFieldCreateAt, model.ReportingSortDirectionAsc, 100, true, false)
|
|
result, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Posts, 12, "should get all 12 posts including deleted")
|
|
|
|
// Verify deleted posts are included
|
|
deletedCount := 0
|
|
for _, post := range result.Posts {
|
|
if post.DeleteAt > 0 {
|
|
deletedCount++
|
|
}
|
|
}
|
|
require.Equal(t, 2, deletedCount, "should have 2 deleted posts")
|
|
})
|
|
|
|
t.Run("Exclude channel metadata system posts", func(t *testing.T) {
|
|
queryParams := buildReportPostQueryParams(channelID, model.ReportingTimeFieldCreateAt, model.ReportingSortDirectionAsc, 100, false, true)
|
|
result, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Posts, 8, "should get 8 posts (10 regular posts - 2 deleted)")
|
|
|
|
// Verify no system posts for channel metadata are included
|
|
for _, post := range result.Posts {
|
|
require.NotEqual(t, model.PostTypeHeaderChange, post.Type)
|
|
require.NotEqual(t, model.PostTypeDisplaynameChange, post.Type)
|
|
require.NotEqual(t, model.PostTypePurposeChange, post.Type)
|
|
}
|
|
})
|
|
|
|
t.Run("Update_at time field", func(t *testing.T) {
|
|
// Simply test that we can query using update_at field
|
|
// Create a new channel for this test
|
|
channelID3 := model.NewId()
|
|
testPosts := []*model.Post{
|
|
{ChannelId: channelID3, UserId: model.NewId(), Message: "test 1"},
|
|
{ChannelId: channelID3, UserId: model.NewId(), Message: "test 2"},
|
|
{ChannelId: channelID3, UserId: model.NewId(), Message: "test 3"},
|
|
}
|
|
|
|
baseTime := model.GetMillis()
|
|
for i, p := range testPosts {
|
|
// Set CreateAt and UpdateAt to different values
|
|
p.CreateAt = baseTime + (int64(i) * 1000)
|
|
p.UpdateAt = baseTime + (int64(i) * 2000) // UpdateAt is different from CreateAt
|
|
saved, err := ss.Post().Save(rctx, p)
|
|
require.NoError(t, err)
|
|
testPosts[i] = saved
|
|
}
|
|
|
|
// Query by update_at field
|
|
queryParams := buildReportPostQueryParams(channelID3, model.ReportingTimeFieldUpdateAt, model.ReportingSortDirectionAsc, 100, false, false)
|
|
result, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Posts, 3, "should get all 3 posts")
|
|
|
|
// Verify that querying by update_at works (posts are ordered by UpdateAt, not CreateAt)
|
|
require.Greater(t, len(result.Posts), 0, "should have posts")
|
|
})
|
|
|
|
t.Run("Timestamp collisions with ID tie-breaking", func(t *testing.T) {
|
|
// Create posts with identical timestamps
|
|
channelID2 := model.NewId()
|
|
sameTime := model.GetMillis()
|
|
collisionPosts := []*model.Post{
|
|
{ChannelId: channelID2, UserId: model.NewId(), Message: "collision 0"},
|
|
{ChannelId: channelID2, UserId: model.NewId(), Message: "collision 1"},
|
|
{ChannelId: channelID2, UserId: model.NewId(), Message: "collision 2"},
|
|
{ChannelId: channelID2, UserId: model.NewId(), Message: "collision 3"},
|
|
{ChannelId: channelID2, UserId: model.NewId(), Message: "collision 4"},
|
|
}
|
|
|
|
for i, p := range collisionPosts {
|
|
p.CreateAt = sameTime
|
|
p.UpdateAt = sameTime
|
|
saved, err := ss.Post().Save(rctx, p)
|
|
require.NoError(t, err)
|
|
collisionPosts[i] = saved // Update with saved post
|
|
}
|
|
|
|
// Paginate through posts with same timestamp
|
|
allPosts := make(map[string]*model.Post)
|
|
iterations := 0
|
|
maxIterations := 10 // Safety limit
|
|
|
|
queryParams := buildReportPostQueryParams(channelID2, model.ReportingTimeFieldCreateAt, model.ReportingSortDirectionAsc, 2, false, false)
|
|
|
|
for iterations < maxIterations {
|
|
result, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err)
|
|
|
|
// Add posts from array to map for deduplication check
|
|
for _, post := range result.Posts {
|
|
allPosts[post.Id] = post
|
|
}
|
|
|
|
if result.NextCursor == nil {
|
|
break
|
|
}
|
|
// Decode cursor for next page
|
|
qp, appErr := model.DecodeReportPostCursorV1(result.NextCursor.Cursor)
|
|
require.Nil(t, appErr)
|
|
qp.PerPage = 2
|
|
queryParams = *qp
|
|
iterations++
|
|
}
|
|
|
|
require.Len(t, allPosts, 5, "should get all 5 posts despite timestamp collision")
|
|
})
|
|
|
|
t.Run("Per page limits", func(t *testing.T) {
|
|
queryParams := buildReportPostQueryParams(channelID, model.ReportingTimeFieldCreateAt, model.ReportingSortDirectionAsc, 3, false, false)
|
|
result, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Posts, 3, "should respect per_page limit")
|
|
})
|
|
|
|
t.Run("Default per page when not specified", func(t *testing.T) {
|
|
queryParams := buildReportPostQueryParams(channelID, model.ReportingTimeFieldCreateAt, model.ReportingSortDirectionAsc, 0, false, false)
|
|
result, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err)
|
|
// Should get all non-deleted posts in one page (default is 100)
|
|
require.Greater(t, len(result.Posts), 0, "should get posts with default per_page")
|
|
})
|
|
|
|
t.Run("Max per page limit enforced", func(t *testing.T) {
|
|
// Test that per_page > 1000 is capped at 1000 by the helper
|
|
queryParams := buildReportPostQueryParams(channelID, model.ReportingTimeFieldCreateAt, model.ReportingSortDirectionAsc, 5000, false, false)
|
|
result, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result, "should handle large per_page values")
|
|
})
|
|
|
|
t.Run("Verify query uses indexes efficiently", func(t *testing.T) {
|
|
// This test verifies that the query plan uses the expected indexes
|
|
// The Posts table has these relevant indexes:
|
|
// - idx_posts_channel_id_update_at (ChannelId, UpdateAt)
|
|
// - idx_posts_channel_id_delete_at_create_at (ChannelId, DeleteAt, CreateAt)
|
|
//
|
|
// For reporting queries, we expect the query to use index seeks, not table scans
|
|
//
|
|
// Note: The actual query plan depends on data distribution and statistics. This test just verifies the query executes
|
|
// efficiently by checking that it completes in a reasonable time.
|
|
|
|
// Create a larger dataset to better test index usage
|
|
largeChannelID := model.NewId()
|
|
largeBaseTime := model.GetMillis()
|
|
numPosts := 100
|
|
|
|
for i := range numPosts {
|
|
post := &model.Post{
|
|
ChannelId: largeChannelID,
|
|
UserId: model.NewId(),
|
|
Message: "large test post",
|
|
CreateAt: largeBaseTime + (int64(i) * 10000),
|
|
UpdateAt: largeBaseTime + (int64(i) * 10000),
|
|
}
|
|
_, err := ss.Post().Save(rctx, post)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Query with ASC direction - should use (ChannelId, CreateAt) index seek
|
|
// This query should be fast (< 100ms) if using indexes properly
|
|
// If it's doing a table scan, it would be much slower with larger datasets
|
|
queryParams := buildReportPostQueryParams(largeChannelID, model.ReportingTimeFieldCreateAt, model.ReportingSortDirectionAsc, 50, false, false)
|
|
result, err := ss.Post().GetPostsForReporting(rctx, queryParams)
|
|
require.NoError(t, err)
|
|
require.Greater(t, len(result.Posts), 0, "should return posts")
|
|
|
|
// Test DESC query which uses different index path
|
|
queryParamsDesc := buildReportPostQueryParams(largeChannelID, model.ReportingTimeFieldCreateAt, model.ReportingSortDirectionDesc, 50, false, false)
|
|
resultDesc, err := ss.Post().GetPostsForReporting(rctx, queryParamsDesc)
|
|
require.NoError(t, err)
|
|
require.Greater(t, len(resultDesc.Posts), 0, "should return posts")
|
|
|
|
// If we got results in reasonable time, the indexes are being used
|
|
// A full table scan would timeout or be noticeably slow
|
|
})
|
|
}
|
|
|
|
func testRestoreContentFlaggedPost(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
channel := &model.Channel{
|
|
DisplayName: "Test Channel",
|
|
Name: "test_channel",
|
|
Type: model.ChannelTypeOpen,
|
|
}
|
|
channel, err := ss.Channel().Save(rctx, channel, -1)
|
|
require.NoError(t, err)
|
|
|
|
botId := model.NewId()
|
|
statusFieldId := model.NewId()
|
|
contentFlaggingManagedFieldId := model.NewId()
|
|
groupId := model.NewId()
|
|
|
|
setupFlaggedPost := func(rootId string) *model.Post {
|
|
post := &model.Post{}
|
|
post.ChannelId = channel.Id
|
|
post.UserId = model.NewId()
|
|
post.Message = NewTestID()
|
|
|
|
if rootId != "" {
|
|
post.RootId = rootId
|
|
}
|
|
|
|
var err error
|
|
post, err = ss.Post().Save(rctx, post)
|
|
require.NoError(t, err)
|
|
|
|
err = ss.Post().Delete(rctx, post.Id, model.GetMillis(), botId)
|
|
require.NoError(t, err)
|
|
|
|
statusPropertyValue := &model.PropertyValue{
|
|
TargetID: post.Id,
|
|
FieldID: statusFieldId,
|
|
Value: fmt.Appendf([]byte{}, "\"%s\"", model.ContentFlaggingStatusPending),
|
|
TargetType: model.PropertyValueTargetTypePost,
|
|
GroupID: groupId,
|
|
}
|
|
_, err = ss.PropertyValue().Create(statusPropertyValue)
|
|
require.NoError(t, err)
|
|
|
|
contentFlaggingManagedPropertyValue := &model.PropertyValue{
|
|
TargetID: post.Id,
|
|
FieldID: contentFlaggingManagedFieldId,
|
|
Value: json.RawMessage("true"),
|
|
TargetType: model.PropertyValueTargetTypePost,
|
|
GroupID: groupId,
|
|
}
|
|
_, err = ss.PropertyValue().Create(contentFlaggingManagedPropertyValue)
|
|
require.NoError(t, err)
|
|
|
|
return post
|
|
}
|
|
|
|
t.Run("Should restore a single root post", func(t *testing.T) {
|
|
post := setupFlaggedPost("")
|
|
|
|
fetchedPost, err := ss.Post().GetSingle(rctx, post.Id, true)
|
|
require.NoError(t, err)
|
|
require.Greater(t, fetchedPost.DeleteAt, int64(0))
|
|
|
|
err = ss.Post().RestoreContentFlaggedPost(post, statusFieldId, contentFlaggingManagedFieldId)
|
|
require.NoError(t, err)
|
|
|
|
fetchedPost, err = ss.Post().GetSingle(rctx, post.Id, false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), fetchedPost.DeleteAt)
|
|
})
|
|
|
|
t.Run("Should restore a thread reply and update thread's reply count", func(t *testing.T) {
|
|
rootPost := &model.Post{}
|
|
rootPost.ChannelId = channel.Id
|
|
rootPost.UserId = model.NewId()
|
|
rootPost.Message = NewTestID()
|
|
|
|
var err error
|
|
rootPost, err = ss.Post().Save(rctx, rootPost)
|
|
require.NoError(t, err)
|
|
|
|
post := setupFlaggedPost(rootPost.Id)
|
|
|
|
err = ss.Post().RestoreContentFlaggedPost(post, statusFieldId, contentFlaggingManagedFieldId)
|
|
require.NoError(t, err)
|
|
|
|
fetchedPost, err := ss.Post().GetSingle(rctx, post.Id, false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), fetchedPost.DeleteAt)
|
|
|
|
thread, err := ss.Thread().Get(rootPost.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(1), thread.ReplyCount)
|
|
})
|
|
}
|
|
|
|
func testDeleteAllPostRemindersForPost(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
t.Run("should permanently delete all reminders for a post", func(t *testing.T) {
|
|
userID1 := NewTestID()
|
|
userID2 := NewTestID()
|
|
userID3 := NewTestID()
|
|
|
|
p1 := &model.Post{
|
|
UserId: userID1,
|
|
ChannelId: NewTestID(),
|
|
Message: "test post",
|
|
Type: model.PostTypeDefault,
|
|
}
|
|
p1, err := ss.Post().Save(rctx, p1)
|
|
require.NoError(t, err)
|
|
|
|
// Create multiple reminders for the same post from different users
|
|
reminder1 := &model.PostReminder{
|
|
TargetTime: 1000,
|
|
PostId: p1.Id,
|
|
UserId: userID1,
|
|
}
|
|
require.NoError(t, ss.Post().SetPostReminder(reminder1))
|
|
|
|
reminder2 := &model.PostReminder{
|
|
TargetTime: 2000,
|
|
PostId: p1.Id,
|
|
UserId: userID2,
|
|
}
|
|
require.NoError(t, ss.Post().SetPostReminder(reminder2))
|
|
|
|
reminder3 := &model.PostReminder{
|
|
TargetTime: 3000,
|
|
PostId: p1.Id,
|
|
UserId: userID3,
|
|
}
|
|
require.NoError(t, ss.Post().SetPostReminder(reminder3))
|
|
|
|
// Verify reminders were created
|
|
var count int
|
|
err = s.GetMaster().Get(&count, `SELECT COUNT(*) FROM PostReminders WHERE PostId=?`, p1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 3, count)
|
|
|
|
// Delete all reminders for the post
|
|
err = ss.Post().DeleteAllPostRemindersForPost(p1.Id)
|
|
require.NoError(t, err)
|
|
|
|
// Verify all reminders were deleted
|
|
err = s.GetMaster().Get(&count, `SELECT COUNT(*) FROM PostReminders WHERE PostId=?`, p1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, count)
|
|
})
|
|
|
|
t.Run("should only delete reminders for the specified post", func(t *testing.T) {
|
|
userID1 := NewTestID()
|
|
userID2 := NewTestID()
|
|
|
|
// Create two posts
|
|
p1 := &model.Post{
|
|
UserId: userID1,
|
|
ChannelId: NewTestID(),
|
|
Message: "test post 1",
|
|
Type: model.PostTypeDefault,
|
|
}
|
|
p1, err := ss.Post().Save(rctx, p1)
|
|
require.NoError(t, err)
|
|
|
|
p2 := &model.Post{
|
|
UserId: userID1,
|
|
ChannelId: NewTestID(),
|
|
Message: "test post 2",
|
|
Type: model.PostTypeDefault,
|
|
}
|
|
p2, err = ss.Post().Save(rctx, p2)
|
|
require.NoError(t, err)
|
|
|
|
// Create reminders for both posts
|
|
reminder1 := &model.PostReminder{
|
|
TargetTime: 1000,
|
|
PostId: p1.Id,
|
|
UserId: userID1,
|
|
}
|
|
require.NoError(t, ss.Post().SetPostReminder(reminder1))
|
|
|
|
reminder2 := &model.PostReminder{
|
|
TargetTime: 2000,
|
|
PostId: p1.Id,
|
|
UserId: userID2,
|
|
}
|
|
require.NoError(t, ss.Post().SetPostReminder(reminder2))
|
|
|
|
reminder3 := &model.PostReminder{
|
|
TargetTime: 3000,
|
|
PostId: p2.Id,
|
|
UserId: userID1,
|
|
}
|
|
require.NoError(t, ss.Post().SetPostReminder(reminder3))
|
|
|
|
reminder4 := &model.PostReminder{
|
|
TargetTime: 4000,
|
|
PostId: p2.Id,
|
|
UserId: userID2,
|
|
}
|
|
require.NoError(t, ss.Post().SetPostReminder(reminder4))
|
|
|
|
// Delete all reminders for post1 only
|
|
err = ss.Post().DeleteAllPostRemindersForPost(p1.Id)
|
|
require.NoError(t, err)
|
|
|
|
// Verify post1 reminders are deleted
|
|
var count int
|
|
err = s.GetMaster().Get(&count, `SELECT COUNT(*) FROM PostReminders WHERE PostId=?`, p1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, count)
|
|
|
|
// Verify post2 reminders are still present
|
|
err = s.GetMaster().Get(&count, `SELECT COUNT(*) FROM PostReminders WHERE PostId=?`, p2.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, count)
|
|
})
|
|
|
|
t.Run("should not error when deleting from post with no reminders", func(t *testing.T) {
|
|
userID := NewTestID()
|
|
|
|
p1 := &model.Post{
|
|
UserId: userID,
|
|
ChannelId: NewTestID(),
|
|
Message: "test post",
|
|
Type: model.PostTypeDefault,
|
|
}
|
|
p1, err := ss.Post().Save(rctx, p1)
|
|
require.NoError(t, err)
|
|
|
|
// Verify no reminders exist
|
|
var count int
|
|
err = s.GetMaster().Get(&count, `SELECT COUNT(*) FROM PostReminders WHERE PostId=?`, p1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, count)
|
|
|
|
// Delete should not error
|
|
err = ss.Post().DeleteAllPostRemindersForPost(p1.Id)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("should not error when deleting from non-existent post", func(t *testing.T) {
|
|
nonExistentPostId := model.NewId()
|
|
|
|
// Delete should not error even for non-existent post
|
|
err := ss.Post().DeleteAllPostRemindersForPost(nonExistentPostId)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("should handle deleting single reminder for post", func(t *testing.T) {
|
|
userID := NewTestID()
|
|
|
|
p1 := &model.Post{
|
|
UserId: userID,
|
|
ChannelId: NewTestID(),
|
|
Message: "test post",
|
|
Type: model.PostTypeDefault,
|
|
}
|
|
p1, err := ss.Post().Save(rctx, p1)
|
|
require.NoError(t, err)
|
|
|
|
// Create a single reminder
|
|
reminder := &model.PostReminder{
|
|
TargetTime: 1000,
|
|
PostId: p1.Id,
|
|
UserId: userID,
|
|
}
|
|
require.NoError(t, ss.Post().SetPostReminder(reminder))
|
|
|
|
// Verify reminder was created
|
|
var count int
|
|
err = s.GetMaster().Get(&count, `SELECT COUNT(*) FROM PostReminders WHERE PostId=?`, p1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, count)
|
|
|
|
// Delete all reminders for the post
|
|
err = ss.Post().DeleteAllPostRemindersForPost(p1.Id)
|
|
require.NoError(t, err)
|
|
|
|
// Verify reminder was deleted
|
|
err = s.GetMaster().Get(&count, `SELECT COUNT(*) FROM PostReminders WHERE PostId=?`, p1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, count)
|
|
})
|
|
}
|