mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* Add cursor-based Posts Reporting API for compliance and auditing Implements a new admin-only endpoint for retrieving posts with efficient cursor-based pagination, designed for compliance, auditing, and archival workflows. Key Features: - Cursor-based pagination using composite (time, ID) keys for consistent performance regardless of dataset size (~10ms per page at any depth) - Flexible time range queries with optional upper/lower bounds - Support for both create_at and update_at time fields - Ascending or descending sort order - Optional metadata enrichment (files, reactions, acknowledgements) - System admin only access (requires manage_system permission) - License enforcement for compliance features API Endpoint: POST /api/v4/reports/posts - Request: JSON body with channel_id, cursor_time, cursor_id, and options - Response: Posts map + next_cursor object (null when pagination complete) - Max page size: 1000 posts per request (MaxReportingPerPage constant) Implementation: - Store Layer: Direct SQL queries with composite index on (ChannelId, CreateAt, Id) - App Layer: Permission checks, optional metadata enrichment, post hooks - API Layer: Parameter validation, system admin enforcement, license checks - Data Model: ReportPostOptions, ReportPostOptionsCursor, ReportPostListResponse Code Quality Improvements: - Added MaxReportingPerPage constant (1000) to eliminate magic numbers - Removed unused StartTime field from ReportPostOptions - Added fmt import for dynamic error messages Testing: - 14 comprehensive store layer unit tests - 12 API layer integration tests covering permissions, pagination, filters - All tests passing Documentation: - POSTS_REPORTING.md: Developer reference with Go structs and usage examples - POSTS_REPORTING_API_SPEC.md: Complete technical specification - GET_POSTS_API_IMPROVEMENTS.md: Implementation analysis and design rationale - POSTS_TIME_RANGE_FEATURE.md: Archived time range feature for future use Performance: Cursor-based pagination maintains consistent ~10ms query time at any dataset depth, compared to offset-based pagination which degrades significantly (Page 1 = 10ms, Page 1000 = 10 seconds). * lint fixes * lint fixes * gofmt * i18n-extract * Add Enterprise license requirement to posts reporting API Enforce Enterprise license (tier 20+) for the new posts reporting endpoint to align with compliance feature licensing. Professional tier is insufficient. Changes: - Add MinimumEnterpriseLicense check in GetPostsForReporting app layer - Add test coverage for license validation (no license and Professional tier) All existing tests pass with new license enforcement. * i18n-extract * add licensing to api documentation * Test SSH signing * Add mmctl command for posts reporting API Adds mmctl report posts command to retrieve posts from a channel for administrative reporting purposes. Supports cursor-based pagination with configurable sorting, filtering, and time range options. Includes database migration for updateat+id index to support efficient cursor-based queries when sorting by update_at. * Refactor posts reporting API cursor to opaque token and improve layer separation This addresses code review feedback by transforming the cursor from exposed fields to an opaque token and improving architectural layer separation. **Key Changes:** 1. **Opaque Cursor Implementation** - Transform cursor from split fields (cursor_time, cursor_id) to single opaque base64-encoded string - Cursor now self-contained with all query parameters embedded - When cursor provided, embedded parameters take precedence over request body - Clients treat cursor as opaque token and pass unchanged 2. **Field Naming** - Rename ExcludeChannelMetadataSystemPosts → ExcludeSystemPosts - Now excludes ALL system posts (any type starting with "system_") - Clearer and more consistent naming 3. **Layer Separation** - Move cursor decoding from store layer to model layer - Create ReportPostQueryParams struct for resolved parameters - Store layer receives pre-resolved parameters (no business logic) - Add ResolveReportPostQueryParams() function in model layer 4. **Code Quality** - Add type-safe constants (ReportingTimeFieldCreateAt, ReportingSortDirectionAsc, etc.) - Replace magic number 9223372036854775807 with math.MaxInt64 - Remove debug SQL logging (info disclosure risk) - Update mmctl to use constants and fix NextCursor pointer access 5. **Tests** - Update all 17 store test calls to use new resolution pattern - Add comprehensive test for DESC + end_time boundary behavior 6. **API Documentation** - Update OpenAPI spec to reflect opaque cursor format - Update all request/response examples - Clarify end_time behavior with sort directions **Files Changed:** - Model layer: public/model/post.go - App layer: channels/app/report.go - Store layer: channels/store/store.go, channels/store/sqlstore/post_store.go - Tests: channels/store/storetest/post_store.go - Mocks: channels/store/storetest/mocks/PostStore.go - API: channels/api4/report.go, channels/api4/report_test.go - mmctl: cmd/mmctl/commands/report.go - Docs: api/v4/source/reports.yaml * Fix unhandled parse errors in cursor decoding Address security finding: cursor decoding was silently ignoring parse errors from strconv functions, which could lead to unexpected behavior when malformed cursors are provided. Changes: - Add explicit error handling for strconv.Atoi (version parsing) - Add explicit error handling for strconv.ParseBool (includeDeleted, excludeSystemPosts) - Add explicit error handling for strconv.ParseInt (timestamp parsing) - Return clear error messages indicating which field failed to parse This prevents silent failures where malformed values would default to zero-values (0, false) and potentially alter query behavior without warning. Addresses DryRun Security finding: "Unhandled Errors in Cursor Parsing" * Fix linting issues - Remove unused reportPostCursorV1 struct (unused) - Remove obsolete +build comment (buildtag) - Use maps.Copy instead of manual loop (mapsloop) - Modernize for loop with range over int (rangeint) - Apply gofmt formatting * Fix gofmt formatting issues Fix alignment in struct literals and constant declarations: - Align map keys in report_test.go request bodies - Align struct fields in ReportPostOptions initialization - Align reporting constant declarations * Update mmctl tests for opaque cursor and add i18n translations Update report_test.go to align with the refactored Posts Reporting API: - Replace split cursor flags (cursor-time, cursor-id) with single opaque cursor flag - Update field name: ExcludeChannelMetadataSystemPosts → ExcludeSystemPosts - Update all mock expectations to use new ReportPostOptionsCursor structure - Replace test cursor values with base64-encoded opaque cursor strings Add English translations for cursor decoding error messages in i18n/en.json. Minor API documentation fix in reports.yaml (remove "all" from description). * Update mmctl tests for opaque cursor and add i18n translations Update report_test.go to align with the refactored Posts Reporting API: - Replace split cursor flags (cursor-time, cursor-id) with single opaque cursor flag - Update field name: ExcludeChannelMetadataSystemPosts → ExcludeSystemPosts - Update all mock expectations to use new ReportPostOptionsCursor structure - Replace test cursor values with base64-encoded opaque cursor strings Add English translations for cursor decoding error messages in i18n/en.json. Minor API documentation fix in reports.yaml (remove "all" from description). * more lint fixes * remove index update files * Remove end_time parameter from Posts Reporting API Align with other cursor-based APIs in the codebase by removing the end_time parameter. The caller now controls when to stop pagination by simply not making another request, which is the same pattern used by GetPostsSinceForSync, MessageExport, and GetPostsBatchForIndexing. Changes: - Remove EndTime field from ReportPostOptions and ReportPostQueryParams - Remove EndTime filtering logic from store layer - Remove tests that used end_time parameter * Refactor posts reporting API for security and validation Address security review feedback by consolidating parameter resolution and validation in the API layer, with comprehensive validation of all cursor fields to prevent SQL injection and invalid queries. Changes: - Move parameter resolution from model to API layer for clearer separation - Add ReportPostQueryParams.Validate() with inline validation for all fields - Validate ChannelId, TimeField, SortDirection, and CursorId format - Add start_time parameter for time-bounded queries - Cap per_page at 100-1000 instead of rejecting invalid values - Export DecodeReportPostCursorV1() for API layer use - Simplify app layer to receive pre-validated parameters - Check channel existence when results are empty (better error messages) Testing: - Add 10 model tests for validation and malformed cursor scenarios - Add 4 API tests for cursors with invalid field values - Refactor 13 store tests to use buildReportPostQueryParams() helper - All 31 tests pass Documentation: - Update OpenAPI spec with start_time, remove unused end_time - Update markdown docs with start_time examples Security improvements: - Whitelist validation prevents SQL injection in TimeField/SortDirection - Format validation ensures ChannelId and CursorId are valid IDs - Single validation point for both cursor and options paths - Defense in depth: validation + parameterized queries + store layer whitelist * Improve posts reporting query efficiency and safety Replace SELECT * and nested OR/AND conditions with explicit column selection and PostgreSQL row value comparison for better performance and maintainability. Changes: - Use postSliceColumns() instead of SELECT * for explicit column selection - Replace Squirrel OR/AND with row value comparison: (timeField, Id) > (?, ?) - Use fmt.Sprintf for safer string formatting in WHERE clause Query improvements: Before: WHERE (CreateAt > ?) OR (CreateAt = ? AND Id > ?) After: WHERE (CreateAt, Id) > (?, ?) Benefits: - Explicit column selection prevents issues if table schema changes - Row value comparison is more concise and better optimized by PostgreSQL - Follows existing patterns in post_store.go (postSliceColumns) - Standard SQL:2003 syntax * Change posts reporting response from map to ordered array Replace the Posts map with an ordered array to preserve query sort order and provide a more natural API response for sequential processing. Changes: - ReportPostListResponse.Posts: map[string]*Post → []*Post - Store layer returns posts array directly (already sorted by query) - App layer iterates by index for metadata enrichment - Remove applyPostsWillBeConsumedHook call (not applicable to reporting) - Update API tests to iterate arrays instead of map lookups - Update store tests to convert array to map for deduplication checks - Remove unused "maps" import Benefits: - Preserves query sort order (ASC/DESC, create_at/update_at) - More natural for sequential processing/export workflows - Simpler response structure for reporting/compliance use cases - Aligns with message export/compliance patterns (no plugin hooks) * Fix linting issues in posts reporting tests Replace inefficient loops with append(...) for better performance. Changes: - Use append(postSlice, result.Posts...) instead of loop - Simplifies code and follows staticcheck recommendations * Fix store test AppError nil checking Use require.Nil instead of require.NoError for *AppError returns to avoid Go interface nil pointer issues. When DecodeReportPostCursorV1 returns nil *AppError and it's assigned to error interface, the interface becomes non-nil even though the pointer is nil. This causes require.NoError to fail incorrectly. --------- Co-authored-by: Mattermost Build <[email protected]> Co-authored-by: Hanzo Dev <[email protected]>
6109 lines
191 KiB
Go
6109 lines
191 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("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("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 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)
|
|
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)
|
|
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)
|
|
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)
|
|
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")
|
|
})
|
|
}
|
|
|
|
// 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 the database (PostgreSQL vs MySQL),
|
|
// 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)
|
|
})
|
|
}
|