Files
team/server/channels/app/import_functions_test.go
9ba7032f5c Cherry-pick: Go 1.25.8 + FIPS password fixes for release-11.5 (#36117)
* ci: shard server Postgres tests into 4 parallel runners (#35739)

* ci: add test sharding plumbing to server CI

Add infrastructure for upcoming test sharding without changing behavior:

- Add shard-index and shard-total inputs to server-test-template.yml
  (defaults preserve existing single-runner behavior)
- Add timing cache restore step (activates only when shard-total > 1)
- Add merge-postgres-test-results job to server-ci.yml that:
  - Merges JUnit XML reports from shard artifacts
  - Saves timing data cache for future shard balancing
  - Handles both single-artifact and multi-shard scenarios
- Add .gitignore entries for timing cache and shard work files

* ci: shard server Postgres tests into 4 parallel runners

Extract sharding logic into standalone, tested scripts and enable
4-shard parallel test execution for server Postgres CI:

Scripts:
- server/scripts/shard-split.js: Node.js bin-packing solver that
  assigns test packages to shards using timing data from previous runs.
  Two-tier strategy: light packages (<2min) whole, heavy packages
  (api4, app) split at individual test level.
- server/scripts/run-shard-tests.sh: Multi-run wrapper that calls
  gotestsum directly for each package group with -run regex filters.
- server/scripts/shard-split.test.js: 8 test cases covering round-robin
  fallback, timing-based balancing, heavy package splitting, JUnit XML
  fallback, and enterprise package separation.

Workflow changes:
- server-test-template.yml: Add shard splitting step that discovers test
  packages and runs the solver. Modified Run Tests step to use wrapper
  script when sharding is active.
- server-ci.yml: Add 4-shard matrix to test-postgres-normal. Update
  merge job artifact patterns for shard-specific names.

Performance: 7.2 min with timing cache vs 62.5 min baseline = 88%
wall-time improvement. First run without cache uses JUnit XML fallback
or round-robin, then populates the cache for subsequent runs.

* fix: raise heavy package threshold to 5 min to preserve test isolation

sqlstore integrity tests scan the entire database and fail when other
packages' test data is present. At 182s, sqlstore was just over the
120s threshold and getting split at test level. Raising to 300s keeps
only api4 (~38 min) and app (~15 min) as heavy — where the real
sharding gains are — while sqlstore, elasticsearch, etc. stay whole
and maintain their test isolation guarantees.

* ci: only save test timing cache on default branch

PR branches always restore from master's timing cache via restore-keys
prefix matching. Timing data is stable day-to-day so this eliminates
cache misses on first PR runs and reduces cache storage.

* ci: skip FIPS tests on PRs (enterprise CI handles compile check)

Per review feedback: the enterprise CI already runs a FIPS compile
check on every PR. Running the full FIPS test suite on PRs is redundant
since it uses the identical test suite as non-FIPS — the only
FIPS-specific failure mode is a build failure from non-approved crypto
imports, which the enterprise compile check catches.

Full FIPS tests continue to run on every push to master.

* fix: address review feedback on run-shard-tests.sh

- Remove set -e so all test runs execute even if earlier ones fail;
  track failures and exit with error at the end (wiggin77)
- Remove unused top-level COVERAGE_FLAG variable (wiggin77)
- Fix RUN_IDX increment position so report, json, and coverage files
  share the same index (wiggin77)
- Update workflow comment: heavy threshold is 5 min, not 2 min (wiggin77)

* style: use node: prefix for built-in fs module in shard-split.js

* fix: avoid interpolating file paths into generated shell script

Read shard package lists from files at runtime instead of interpolating
them into the generated script via printf. This prevents theoretical
shell metacharacter injection from directory names, as flagged by
DryRun Security.

* fix(ci): rename merged artifact to match server-ci-report glob

The merged artifact was named postgres-server-test-logs-merged which
does not match the *-test-logs pattern in server-ci-report.yml,
causing Postgres test results to be missing from PR/commit reports.

Also pins junit-report-merger to exact version 7.0.0 for supply chain
safety.

* fix(ci): pass RACE_MODE env into Docker container

RACE_MODE was set on the host runner but never included in the docker
run --env list. The light-package path worked because the heredoc
expanded on the host, but run-shard-tests.sh reads RACE_MODE at
runtime inside the container where it was unset. This caused heavy
packages (api4, app) to silently lose -race detection.

* fix(ci): discover new tests in heavy packages not in timing cache

Tests not present in the timing cache (newly added or renamed) would
not appear in any shard -run regex, causing them to silently skip.
After building items from the cache, run go test -list to discover
current test names and assign any cache-missing tests to shards via
the normal bin-packing algorithm with a small default duration.

* fix(ci): add missing line continuation backslash in docker run

The previous --env FIPS_ENABLED line was missing a trailing backslash
after adding --env RACE_MODE, causing docker run to see a truncated
command and fail with "requires at least 1 argument".

* fix(ci): add setup-go step for shard test discovery

go test -list in shard-split.js runs on the host runner via execSync,
but Go is only available inside the Docker container. Without this
step, every invocation fails silently and new-test discovery is a
no-op. Adding actions/setup-go before the shard split step ensures
the Go toolchain is available on the host.

---------

* updated go to version 1.25.8 (#35817)

* updated go to version 1.25.8

* updated gotestsum version to work with go 1.25.8

go 1.25 does not work with indirect tools 0.11 dependency pulled by
gotestsum.

* Use sync.WaitGroup.Go to simplify goroutine creation

Replace the wg.Add(1) + go func() { defer wg.Done() }() pattern with
wg.Go(), which was introduced in Go 1.25.

* pushes fips image on workflow dispatch to allow fips test to run on go version update

* fix new requirements for FIPS compliance imposed on updating to go 1.25.8

* updates openssl symbol check for library shipped with FIPS new versions

go-openssl v2 shipped with FIPS versions starting from 1.25 uses mkcgo to generate
bindings causing symbol names to be different.

* removes temp workflow-dispatch condition

* keep versions out of agents md file

* Move password hashers from server/v8 to server/public to fix module layering violation (#35805)a

* Move password hashers from server/v8 to server/public to fix layering violation

* Revert "Move password hashers from server/v8 to server/public to fix layering violation"

This reverts commit 8cad5b8dc93df032d62f20634efff7398dcd76f0.

* invert dependency between hashers and model

* make modules-tidy

---------

Co-authored-by: Jesse Hallam <[email protected]>
Co-authored-by: Mattermost Build <[email protected]>

* Generate instead of hard-coding test passwords, enforce new minimum for FIPS, shard CI, fix FIPS builds (#35905)

* Replace hardcoded test passwords with model.NewTestPassword()

Add model.NewTestPassword() utility that generates 14+ character
passwords meeting complexity requirements for FIPS compliance. Replace
all short hardcoded test passwords across the test suite with calls to
this function.

* Enforce FIPS compliance for passwords and HMAC keys

FIPS OpenSSL requires HMAC keys to be at least 14 bytes. PBKDF2 uses
the password as the HMAC key internally, so short passwords cause
PKCS5_PBKDF2_HMAC to fail.

- Add FIPSEnabled and PasswordFIPSMinimumLength build-tag constants
- Raise the password minimum length floor to 14 when compiled with
  requirefips, applied in SetDefaults only when unset and validated
  independently in IsValid
- Return ErrMismatchedHashAndPassword for too-short passwords in
  PBKDF2 CompareHashAndPassword rather than a cryptic OpenSSL error
- Validate atmos/camo HMAC key length under FIPS and lengthen test
  keys accordingly
- Adjust password validation tests to use PasswordFIPSMinimumLength
  so they work under both FIPS and non-FIPS builds

* CI: shard FIPS test suite and extract merge template

Run FIPS tests on PRs that touch go.mod or have 'fips' in the branch
name. Shard FIPS tests across 4 runners matching the normal Postgres
suite. Extract the test result merge logic into a reusable workflow
template to deduplicate the normal and FIPS merge jobs.

* more

* Fix email test helper to respect FIPS minimum password length

* Fix test helpers to respect FIPS minimum password length

* Remove unnecessary "disable strict password requirements" blocks from test helpers

* Fix CodeRabbit review comments on PR #35905

- Add server-test-merge-template.yml to server-ci.yml pull_request.paths
  so changes to the reusable merge workflow trigger Server CI validation
- Skip merge-postgres-fips-test-results job when test-postgres-normal-fips
  was skipped, preventing failures due to missing artifacts
- Set guest.Password on returned guest in CreateGuestAndClient helper
  to keep contract consistent with CreateUserWithClient
- Use shared LowercaseLetters/UppercaseLetters/NUMBERS/PasswordFIPSMinimumLength
  constants in NewTestPassword() to avoid drift if FIPS floor changes

* Rename FIPS test artifact to match server-ci-report pattern

The server-ci-report job searches for artifacts matching "*-test-logs",
so rename from postgres-server-test-logs-fips to
postgres-server-fips-test-logs to be included in the report.

---------

* upgrade golangci-lint (#35845)

* feat: automatically trigger fips e2e tests for relevant prs (#36014)

Co-authored-by: Mattermost Build <[email protected]>

* Fix FIPS-incompatible passwords and config in e2e test suites (#36001)

* Fix FIPS-incompatible passwords and config in e2e test suites

FIPS OpenSSL requires PBKDF2 HMAC keys >= 14 bytes, so the server
now enforces PasswordSettings.MinimumLength >= 14 under FIPS builds.
The e2e suites were failing (Cypress 0/447, Playwright 16/353) because
they used short passwords and set MinimumLength below the FIPS floor.

- Add newTestPassword() to both Cypress and Playwright utilities,
  returning a static FIPS-compliant password (>= 14 chars)
- Replace all short hardcoded Mattermost user passwords ('passwd',
  'Test123456!', 'Testing123', 'Password123!') with newTestPassword()
- Raise PasswordSettings.MinimumLength from 5 to 14 in default configs
- Update password validation tests for the FIPS minimum (error messages,
  default expectations, test password lengths)

* Fix additional FIPS e2e test failures

- Revert server-constant-dependent assertions (MinimumLength defaults
  and validation errors) since those depend on FIPS build tags, not config
- Fix short hardcoded passwords in signup, forgot_password, and
  existing_email_address specs to use newTestPassword()
- Fix password_spec cancel test to use a different password than
  testUser.password (both resolve to newTestPassword())
- Fix Playwright password reset test to use newTestPassword() instead
  of pw.random.id() (7 chars, too short for MinimumLength=14)
- Update password help text assertions to match MinimumLength=14 config
- Use regex for Playwright signup page password error locator
- Fix import order in ABAC support.ts

* Fix FIPS vs non-FIPS build-dependent e2e test assertions

The server's password minimum constants differ between FIPS (14) and
non-FIPS (8/5) builds. Make three test assertions adaptive:

- MM-T1770: Accept default MinimumLength of either 8 or 14
- MM-T1771: Match validation error with either 5 or 14 as the minimum
- MM-T1773: Reload after save and compare against actual server config,
  since saving MinimumLength=5 (webapp default) is rejected on FIPS

* e2e: upgrade demo plugin to v0.11.0 and configure required settings

* update demo plugin download link

---------

Co-authored-by: sabril <[email protected]>

* fix(cypress): demo plugin (#36056)

* test: clean up channel store data after TestChannelStore (#36066)

TestChannelStore sub-tests create channels, members, and team members
using fake TeamIds and UserIds (model.NewId() for non-existent rows).
These records are left in the database and cause integrity tests
(TestCheck*) running in the same binary to fail their full-table scans.

Register a t.Cleanup on TestChannelStore that purges the affected
tables entirely. A blanket purge is safe: the schema enforces no FK
constraints, and every test suite creates its own data independently.

* Fix command injection in server-test-template workflow (#36080)

Replace the unquoted heredoc (which embedded GITHUB_HEAD_REF into a
generated script) with a cp of the existing run-shard-tests.sh, which
already handles the light-only case. Pass BUILD_NUMBER and TEST_TARGET
as explicit docker env vars instead of interpolating them into script
content.

* fix(ci): restore testname format in sharded gotestsum runs (#36078)

run-shard-tests.sh called gotestsum directly without --format, so it
fell back to gotestsum's default (pkgname) instead of the testname
format set by the Makefile. Pass --format "${GOTESTSUM_FORMAT:-testname}"
to match the Makefile default.

Co-authored-by: Mattermost Build <[email protected]>

* fix: remove UpdatePostsChannelTypeByChannelId/BackfillPostsChannelType from release-11.5

These functions were added to master but not backported to release-11.5.
The golangci-lint cherry-pick incorrectly included them via conflict resolution.

* style: gofmt elasticsearch/opensearch files after conflict resolution

* fix: remove actions.spec.ts (references SystemConsolePage.users, absent on release-11.5)

---------

Co-authored-by: Pavel Zeman <[email protected]>

Co-authored-by: Carlos Garcia <[email protected]>
Co-authored-by: Christopher Speller <[email protected]>
Co-authored-by: Mattermost Build <[email protected]>

Co-authored-by: sabril <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-04-16 13:49:18 +02:00

6096 lines
216 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"archive/zip"
"context"
"io"
"os"
"path/filepath"
"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/mlog"
"github.com/mattermost/mattermost/server/v8/channels/app/imports"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/mattermost/mattermost/server/v8/channels/testlib"
"github.com/mattermost/mattermost/server/v8/channels/utils"
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
"github.com/mattermost/mattermost/server/v8/platform/shared/filestore"
)
func TestImportImportScheme(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
// Mark the phase 2 permissions migration as completed.
err := th.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
require.NoError(t, err, "Failed to save system value.")
defer func() {
_, err = th.App.Srv().Store().System().PermanentDeleteByName(model.MigrationKeyAdvancedPermissionsPhase2)
require.NoError(t, err, "Failed to delete system value.")
}()
// Try importing an invalid scheme in dryRun mode.
data := imports.SchemeImportData{
Name: model.NewPointer(model.NewId()),
Scope: model.NewPointer("team"),
DefaultTeamGuestRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultTeamUserRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultTeamAdminRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultChannelGuestRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultChannelUserRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultChannelAdminRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
Description: model.NewPointer("description"),
}
appErr := th.App.importScheme(th.Context, &data, true)
require.NotNil(t, appErr, "Should have failed to import.")
_, err = th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.Error(t, err, "Scheme should not have imported.")
// Try importing a valid scheme in dryRun mode.
data.DisplayName = model.NewPointer("display name")
appErr = th.App.importScheme(th.Context, &data, true)
require.Nil(t, appErr, "Should have succeeded.")
_, err = th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.Error(t, err, "Scheme should not have imported.")
// Try importing an invalid scheme.
data.DisplayName = nil
appErr = th.App.importScheme(th.Context, &data, false)
require.NotNil(t, appErr, "Should have failed to import.")
_, err = th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.Error(t, err, "Scheme should not have imported.")
// Try importing a valid scheme with all params set.
data.DisplayName = model.NewPointer("display name")
appErr = th.App.importScheme(th.Context, &data, false)
require.Nil(t, appErr, "Should have succeeded.")
scheme, nErr := th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.NoError(t, nErr, "Failed to import scheme: %v", err)
assert.Equal(t, *data.Name, scheme.Name)
assert.Equal(t, *data.DisplayName, scheme.DisplayName)
assert.Equal(t, *data.Description, scheme.Description)
assert.Equal(t, *data.Scope, scheme.Scope)
role, nErr := th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamAdminRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamAdminRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamUserRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamUserRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamGuestRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamGuestRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelAdminRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelAdminRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelUserRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelUserRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelGuestRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelGuestRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
// Try modifying all the fields and re-importing.
data.DisplayName = model.NewPointer("new display name")
data.Description = model.NewPointer("new description")
appErr = th.App.importScheme(th.Context, &data, false)
require.Nil(t, appErr, "Should have succeeded: %v", err)
scheme, nErr = th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.NoError(t, nErr, "Failed to import scheme: %v", err)
assert.Equal(t, *data.Name, scheme.Name)
assert.Equal(t, *data.DisplayName, scheme.DisplayName)
assert.Equal(t, *data.Description, scheme.Description)
assert.Equal(t, *data.Scope, scheme.Scope)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamAdminRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamAdminRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamUserRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamUserRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamGuestRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamGuestRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelAdminRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelAdminRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelUserRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelUserRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelGuestRole)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelGuestRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
// Try changing the scope of the scheme and reimporting.
data.Scope = model.NewPointer("channel")
appErr = th.App.importScheme(th.Context, &data, false)
require.NotNil(t, appErr, "Should have failed to import.")
scheme, nErr = th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.NoError(t, nErr, "Failed to import scheme: %v", err)
assert.Equal(t, *data.Name, scheme.Name)
assert.Equal(t, *data.DisplayName, scheme.DisplayName)
assert.Equal(t, *data.Description, scheme.Description)
assert.Equal(t, "team", scheme.Scope)
}
func TestImportImportSchemeWithoutGuestRoles(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
// Mark the phase 2 permissions migration as completed.
err := th.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
require.NoError(t, err, "Failed to save system value.")
defer func() {
_, err = th.App.Srv().Store().System().PermanentDeleteByName(model.MigrationKeyAdvancedPermissionsPhase2)
require.NoError(t, err, "Failed to delete system value.")
}()
// Try importing an invalid scheme in dryRun mode.
data := imports.SchemeImportData{
Name: model.NewPointer(model.NewId()),
Scope: model.NewPointer("team"),
DefaultTeamUserRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultTeamAdminRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultChannelUserRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultChannelAdminRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
Description: model.NewPointer("description"),
}
appErr := th.App.importScheme(th.Context, &data, true)
require.NotNil(t, appErr, "Should have failed to import.")
_, err = th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.Error(t, err, "Scheme should not have imported.")
// Try importing a valid scheme in dryRun mode.
data.DisplayName = model.NewPointer("display name")
appErr = th.App.importScheme(th.Context, &data, true)
require.Nil(t, appErr, "Should have succeeded.")
_, err = th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.Error(t, err, "Scheme should not have imported.")
// Try importing an invalid scheme.
data.DisplayName = nil
appErr = th.App.importScheme(th.Context, &data, false)
require.NotNil(t, appErr, "Should have failed to import.")
_, err = th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.Error(t, err, "Scheme should not have imported.")
// Try importing a valid scheme with all params set.
data.DisplayName = model.NewPointer("display name")
appErr = th.App.importScheme(th.Context, &data, false)
require.Nil(t, appErr, "Should have succeeded.")
scheme, err := th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.NoError(t, err, "Failed to import scheme: %v", err)
assert.Equal(t, *data.Name, scheme.Name)
assert.Equal(t, *data.DisplayName, scheme.DisplayName)
assert.Equal(t, *data.Description, scheme.Description)
assert.Equal(t, *data.Scope, scheme.Scope)
role, err := th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamAdminRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamAdminRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, err = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamUserRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamUserRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, err = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamGuestRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamGuestRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, err = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelAdminRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelAdminRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, err = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelUserRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelUserRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, err = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelGuestRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelGuestRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
// Try modifying all the fields and re-importing.
data.DisplayName = model.NewPointer("new display name")
data.Description = model.NewPointer("new description")
appErr = th.App.importScheme(th.Context, &data, false)
require.Nil(t, appErr, "Should have succeeded: %v", err)
scheme, err = th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.NoError(t, err, "Failed to import scheme: %v", err)
assert.Equal(t, *data.Name, scheme.Name)
assert.Equal(t, *data.DisplayName, scheme.DisplayName)
assert.Equal(t, *data.Description, scheme.Description)
assert.Equal(t, *data.Scope, scheme.Scope)
role, err = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamAdminRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamAdminRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, err = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamUserRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamUserRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, err = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultTeamGuestRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultTeamGuestRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, err = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelAdminRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelAdminRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, err = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelUserRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelUserRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
role, err = th.App.Srv().Store().Role().GetByName(context.Background(), scheme.DefaultChannelGuestRole)
require.NoError(t, err, "Should have found the imported role.")
assert.Equal(t, *data.DefaultChannelGuestRole.DisplayName, role.DisplayName)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
// Try changing the scope of the scheme and reimporting.
data.Scope = model.NewPointer("channel")
appErr = th.App.importScheme(th.Context, &data, false)
require.NotNil(t, appErr, "Should have failed to import.")
scheme, err = th.App.Srv().Store().Scheme().GetByName(*data.Name)
require.NoError(t, err, "Failed to import scheme: %v", err)
assert.Equal(t, *data.Name, scheme.Name)
assert.Equal(t, *data.DisplayName, scheme.DisplayName)
assert.Equal(t, *data.Description, scheme.Description)
assert.Equal(t, "team", scheme.Scope)
}
func TestImportImportRole(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
// Try importing an invalid role in dryRun mode.
rid1 := model.NewId()
data := imports.RoleImportData{
Name: &rid1,
}
appErr := th.App.importRole(th.Context, &data, true)
require.NotNil(t, appErr, "Should have failed to import.")
_, nErr := th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
require.Error(t, nErr, "Should have failed to import.")
// Try importing the valid role in dryRun mode.
data.DisplayName = model.NewPointer("display name")
appErr = th.App.importRole(th.Context, &data, true)
require.Nil(t, appErr, "Should have succeeded.")
_, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
require.Error(t, nErr, "Role should not have imported as we are in dry run mode.")
// Try importing an invalid role.
data.DisplayName = nil
appErr = th.App.importRole(th.Context, &data, false)
require.NotNil(t, appErr, "Should have failed to import.")
_, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
require.Error(t, nErr, "Role should not have imported.")
// Try importing a valid role with all params set.
data.DisplayName = model.NewPointer("display name")
data.Description = model.NewPointer("description")
data.Permissions = &[]string{"invite_user", "add_user_to_team"}
appErr = th.App.importRole(th.Context, &data, false)
require.Nil(t, appErr, "Should have succeeded.")
role, nErr := th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.Name, role.Name)
assert.Equal(t, *data.DisplayName, role.DisplayName)
assert.Equal(t, *data.Description, role.Description)
assert.Equal(t, *data.Permissions, role.Permissions)
assert.False(t, role.BuiltIn)
assert.False(t, role.SchemeManaged)
// Try changing all the params and reimporting.
data.DisplayName = model.NewPointer("new display name")
data.Description = model.NewPointer("description")
data.Permissions = &[]string{"manage_slash_commands"}
data.SchemeManaged = model.NewPointer(true)
appErr = th.App.importRole(th.Context, &data, false)
require.Nil(t, appErr, "Should have succeeded. %v", appErr)
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data.Name, role.Name)
assert.Equal(t, *data.DisplayName, role.DisplayName)
assert.Equal(t, *data.Description, role.Description)
assert.Equal(t, *data.Permissions, role.Permissions)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
// Check that re-importing with only required fields doesn't update the others.
data2 := imports.RoleImportData{
Name: &rid1,
DisplayName: model.NewPointer("new display name again"),
}
appErr = th.App.importRole(th.Context, &data2, false)
require.Nil(t, appErr, "Should have succeeded.")
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
require.NoError(t, nErr, "Should have found the imported role.")
assert.Equal(t, *data2.Name, role.Name)
assert.Equal(t, *data2.DisplayName, role.DisplayName)
assert.Equal(t, *data.Description, role.Description)
assert.Equal(t, *data.Permissions, role.Permissions)
assert.False(t, role.BuiltIn)
assert.True(t, role.SchemeManaged)
}
func TestImportImportTeam(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
// Mark the phase 2 permissions migration as completed.
err := th.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
require.NoError(t, err, "Failed to save system value.")
defer func() {
_, err = th.App.Srv().Store().System().PermanentDeleteByName(model.MigrationKeyAdvancedPermissionsPhase2)
require.NoError(t, err, "Failed to delete system value.")
}()
scheme1 := th.SetupTeamScheme(t)
scheme2 := th.SetupTeamScheme(t)
// Check how many teams are in the database.
teamsCount, err := th.App.Srv().Store().Team().AnalyticsTeamCount(nil)
require.NoError(t, err, "Failed to get team count.")
// we also assert that the team name can be upper case
// Note there are no reserved team names starting with `Z`, making this flake-free.
teamName := "Z" + model.NewId()
sanitizedTeamName := strings.ToLower(teamName)
data := imports.TeamImportData{
Name: model.NewPointer(teamName),
DisplayName: model.NewPointer("Display Name"),
Type: model.NewPointer("XYZ"),
Description: model.NewPointer("The team description."),
AllowOpenInvite: model.NewPointer(true),
Scheme: &scheme1.Name,
}
// Try importing an invalid team in dryRun mode.
err = th.App.importTeam(th.Context, &data, true)
require.Error(t, err, "Should have received an error importing an invalid team.")
// Do a valid team in dry-run mode.
data.Type = model.NewPointer("O")
appErr := th.App.importTeam(th.Context, &data, true)
require.Nil(t, appErr, "Received an error validating valid team.")
// Check that no more teams are in the DB.
th.CheckTeamCount(t, teamsCount)
// Do an invalid team in apply mode, check db changes.
data.Type = model.NewPointer("XYZ")
err = th.App.importTeam(th.Context, &data, false)
require.Error(t, err, "Import should have failed on invalid team.")
// Check that no more teams are in the DB.
th.CheckTeamCount(t, teamsCount)
// Do a valid team in apply mode, check db changes.
data.Type = model.NewPointer("O")
appErr = th.App.importTeam(th.Context, &data, false)
require.Nil(t, appErr, "Received an error importing valid team: %v", err)
// Check that one more team is in the DB.
th.CheckTeamCount(t, teamsCount+1)
// Get the team and check that all the fields are correct.
team, appErr := th.App.GetTeamByName(sanitizedTeamName)
require.Nil(t, appErr, "Failed to get team from database.")
assert.Equal(t, *data.DisplayName, team.DisplayName)
assert.Equal(t, *data.Type, team.Type)
assert.Equal(t, *data.Description, team.Description)
assert.Equal(t, *data.AllowOpenInvite, team.AllowOpenInvite)
assert.Equal(t, scheme1.Id, *team.SchemeId)
// Alter all the fields of that team (apart from unique identifier) and import again.
data.DisplayName = model.NewPointer("Display Name 2")
data.Type = model.NewPointer("P")
data.Description = model.NewPointer("The new description")
data.AllowOpenInvite = model.NewPointer(false)
data.Scheme = &scheme2.Name
// Check that the original number of teams are again in the DB (because this query doesn't include deleted).
data.Type = model.NewPointer("O")
appErr = th.App.importTeam(th.Context, &data, false)
require.Nil(t, appErr, "Received an error importing updated valid team.")
th.CheckTeamCount(t, teamsCount+1)
// Get the team and check that all fields are correct.
team, appErr = th.App.GetTeamByName(sanitizedTeamName)
require.Nil(t, appErr, "Failed to get team from database.")
assert.Equal(t, *data.DisplayName, team.DisplayName)
assert.Equal(t, *data.Type, team.Type)
assert.Equal(t, *data.Description, team.Description)
assert.Equal(t, *data.AllowOpenInvite, team.AllowOpenInvite)
assert.Equal(t, scheme2.Id, *team.SchemeId)
}
func TestImportImportChannel(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
// Mark the phase 2 permissions migration as completed.
err := th.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
require.NoError(t, err, "Failed to save system value.")
defer func() {
_, err = th.App.Srv().Store().System().PermanentDeleteByName(model.MigrationKeyAdvancedPermissionsPhase2)
require.NoError(t, err, "Failed to delete system value.")
}()
scheme1 := th.SetupChannelScheme(t)
scheme2 := th.SetupChannelScheme(t)
// Import a Team.
teamName := model.NewRandomTeamName()
appErr := th.App.importTeam(th.Context, &imports.TeamImportData{
Name: &teamName,
DisplayName: model.NewPointer("Display Name"),
Type: model.NewPointer("O"),
}, false)
require.Nil(t, appErr, "Failed to import team.")
team, appErr := th.App.GetTeamByName(teamName)
require.Nil(t, appErr, "Failed to get team from database.")
// Check how many channels are in the database.
channelCount, nErr := th.App.Srv().Store().Channel().AnalyticsTypeCount("", model.ChannelTypeOpen)
require.NoError(t, nErr, "Failed to get team count.")
// Do an invalid channel in dry-run mode.
chanOpen := model.ChannelTypeOpen
data := imports.ChannelImportData{
Team: &teamName,
DisplayName: model.NewPointer("Display Name"),
Type: &chanOpen,
Header: model.NewPointer("Channel Header"),
Purpose: model.NewPointer("Channel Purpose"),
Scheme: &scheme1.Name,
}
appErr = th.App.importChannel(th.Context, &data, true)
require.NotNil(t, appErr, "Expected error due to invalid name.")
// Check that no more channels are in the DB.
th.CheckChannelsCount(t, channelCount)
// Do a valid channel with a nonexistent team in dry-run mode.
data.Name = model.NewPointer("channelname")
data.Team = model.NewPointer(model.NewId())
appErr = th.App.importChannel(th.Context, &data, true)
require.Nil(t, appErr, "Expected success as cannot validate channel name in dry run mode.")
// Check that no more channels are in the DB.
th.CheckChannelsCount(t, channelCount)
// Do a valid channel in dry-run mode.
data.Team = &teamName
appErr = th.App.importChannel(th.Context, &data, true)
require.Nil(t, appErr, "Expected success as valid team.")
// Check that no more channels are in the DB.
th.CheckChannelsCount(t, channelCount)
// Do an invalid channel in apply mode.
data.Name = nil
appErr = th.App.importChannel(th.Context, &data, false)
require.NotNil(t, appErr, "Expected error due to invalid name (apply mode).")
// Check that no more channels are in the DB.
th.CheckChannelsCount(t, channelCount)
// Do a valid channel in apply mode with a non-existent team.
data.Name = model.NewPointer("channelname")
data.Team = model.NewPointer(model.NewId())
appErr = th.App.importChannel(th.Context, &data, false)
require.NotNil(t, appErr, "Expected error due to non-existent team (apply mode).")
// Check that no more channels are in the DB.
th.CheckChannelsCount(t, channelCount)
// Do a valid channel in apply mode.
data.Team = &teamName
// we also assert that the channel name can be upper case
// for the import workflow
data.Name = model.NewPointer("channelName")
sanitizedChannelName := strings.ToLower(*data.Name)
appErr = th.App.importChannel(th.Context, &data, false)
require.Nil(t, appErr, "Expected success in apply mode")
// Check that 1 more channel is in the DB.
th.CheckChannelsCount(t, channelCount+1)
// Get the Channel and check all the fields are correct.
channel, appErr := th.App.GetChannelByName(th.Context, sanitizedChannelName, team.Id, false)
require.Nil(t, appErr, "Failed to get channel from database.")
assert.Equal(t, sanitizedChannelName, channel.Name)
assert.Equal(t, *data.DisplayName, channel.DisplayName)
assert.Equal(t, *data.Type, channel.Type)
assert.Equal(t, *data.Header, channel.Header)
assert.Equal(t, *data.Purpose, channel.Purpose)
assert.Equal(t, scheme1.Id, *channel.SchemeId)
// Alter all the fields of that channel.
cTypePr := model.ChannelTypePrivate
data.DisplayName = model.NewPointer("Changed Disp Name")
data.Type = &cTypePr
data.Header = model.NewPointer("New Header")
data.Purpose = model.NewPointer("New Purpose")
data.Scheme = &scheme2.Name
appErr = th.App.importChannel(th.Context, &data, false)
require.Nil(t, appErr, "Expected success in apply mode")
// Check channel count the same.
th.CheckChannelsCount(t, channelCount)
// Get the Channel and check all the fields are correct.
channel, appErr = th.App.GetChannelByName(th.Context, sanitizedChannelName, team.Id, false)
require.Nil(t, appErr, "Failed to get channel from database.")
assert.Equal(t, sanitizedChannelName, channel.Name)
assert.Equal(t, *data.DisplayName, channel.DisplayName)
assert.Equal(t, *data.Type, channel.Type)
assert.Equal(t, *data.Header, channel.Header)
assert.Equal(t, *data.Purpose, channel.Purpose)
assert.Equal(t, scheme2.Id, *channel.SchemeId)
// Do a valid archived channel.
now := model.GetMillis()
data.Name = model.NewPointer("archivedchannel")
data.DisplayName = model.NewPointer("Archived Channel")
data.Type = &chanOpen
data.Header = model.NewPointer("Archived Channel Header")
data.Purpose = model.NewPointer("Archived Channel Purpose")
data.Scheme = &scheme1.Name
data.DeletedAt = &now
appErr = th.App.importChannel(th.Context, &data, false)
require.Nil(t, appErr, "Expected success in apply mode")
aChan, appErr := th.App.GetChannelByName(th.Context, sanitizedChannelName, team.Id, true)
require.Nil(t, appErr, "Failed to get channel from database.")
assert.Equal(t, sanitizedChannelName, aChan.Name)
}
func TestImportImportUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
// Check how many users are in the database.
userCount, cErr := th.App.Srv().Store().User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
})
require.NoError(t, cErr, "Failed to get user count.")
t.Run("import an invalid user in dry-run", func(t *testing.T) {
data := imports.UserImportData{
Username: model.NewPointer(model.NewUsername()),
}
appErr := th.App.importUser(th.Context, &data, true)
require.NotNil(t, appErr, "Should have failed to import invalid user.")
// Check that no more users are in the DB.
userCountCurrent, err := th.App.Srv().Store().User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
})
require.NoError(t, err, "Failed to get user count.")
assert.Equal(t, userCount, userCountCurrent, "Unexpected number of users")
})
t.Run("import a valid user in dry-run", func(t *testing.T) {
data := imports.UserImportData{
Username: model.NewPointer(model.NewUsername()),
Email: model.NewPointer(model.NewId() + "@example.com"),
}
appErr := th.App.importUser(th.Context, &data, true)
require.Nil(t, appErr, "Should have succeeded to import valid user.")
// Check that no more users are in the DB.
userCountCurrent, err := th.App.Srv().Store().User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
})
require.NoError(t, err, "Failed to get user count.")
assert.Equal(t, userCount, userCountCurrent, "Unexpected number of users")
})
t.Run("import an invalid user in apply mode", func(t *testing.T) {
data := imports.UserImportData{
Username: model.NewPointer(model.NewUsername()),
}
appErr := th.App.importUser(th.Context, &data, false)
require.NotNil(t, appErr, "Should have failed to import invalid user.")
// Check that no more users are in the DB.
userCountCurrent, err := th.App.Srv().Store().User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
})
require.NoError(t, err, "Failed to get user count.")
assert.Equal(t, userCount, userCountCurrent, "Unexpected number of users")
})
t.Run("import a valid user in apply mode", func(t *testing.T) {
username := "A" + model.NewUsername()[1:]
testsDir, _ := fileutils.FindDir("tests")
data := imports.UserImportData{
Avatar: imports.Avatar{
ProfileImage: model.NewPointer(filepath.Join(testsDir, "test.png")),
},
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
Nickname: model.NewPointer(model.NewId()),
FirstName: model.NewPointer(model.NewId()),
LastName: model.NewPointer(model.NewId()),
Position: model.NewPointer(model.NewId()),
}
appErr := th.App.importUser(th.Context, &data, false)
require.Nil(t, appErr, "Should have succeeded to import valid user.")
// Check that one more user is in the DB.
userCountCurrent, err := th.App.Srv().Store().User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
})
require.NoError(t, err, "Failed to get user count.")
userCount++ // Increment the user count.
assert.Equal(t, userCount, userCountCurrent, "Unexpected number of users")
// Get the user and check all the fields are correct.
user, err2 := th.App.GetUserByUsername(username)
require.Nil(t, err2, "Failed to get user from database.")
assert.Equal(t, *data.Email, user.Email)
assert.Equal(t, *data.Nickname, user.Nickname)
assert.Equal(t, *data.FirstName, user.FirstName)
assert.Equal(t, *data.LastName, user.LastName)
assert.Equal(t, *data.Position, user.Position)
// Check calculated properties.
require.Equal(t, strings.ToLower(username), user.Username, "Expected Username to be lower case.")
require.Empty(t, user.AuthService, "Expected Auth Service to be empty.")
require.Empty(t, user.AuthData, "Expected AuthData to be empty.")
require.NotEmpty(t, user.Password, "Expected password to be set.")
require.True(t, user.EmailVerified, "Expected EmailVerified to be true.")
require.Equal(t, user.Locale, *th.App.Config().LocalizationSettings.DefaultClientLocale, "Expected Locale to be the default.")
require.Equal(t, user.Roles, "system_user", "Expected roles to be system_user")
})
t.Run("import a valid user where there is an existing user", func(t *testing.T) {
username := model.NewUsername()
testsDir, _ := fileutils.FindDir("tests")
data := imports.UserImportData{
Avatar: imports.Avatar{
ProfileImage: model.NewPointer(filepath.Join(testsDir, "test.png")),
},
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
Nickname: model.NewPointer(model.NewId()),
FirstName: model.NewPointer(model.NewId()),
LastName: model.NewPointer(model.NewId()),
Position: model.NewPointer(model.NewId()),
}
appErr := th.App.importUser(th.Context, &data, false)
require.Nil(t, appErr, "Should have succeeded to import valid user.")
// Check that one more user is in the DB.
userCountCurrent, err := th.App.Srv().Store().User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
})
require.NoError(t, err, "Failed to get user count.")
userCount++ // Increment the user count.
assert.Equal(t, userCount, userCountCurrent, "Unexpected number of users")
// Alter all the fields of that user.
data.Email = model.NewPointer(model.NewId() + "@example.com")
data.ProfileImage = model.NewPointer(filepath.Join(testsDir, "testgif.gif"))
data.AuthService = model.NewPointer("ldap")
data.AuthData = &username
data.Nickname = model.NewPointer(model.NewId())
data.FirstName = model.NewPointer(model.NewId())
data.LastName = model.NewPointer(model.NewId())
data.Position = model.NewPointer(model.NewId())
data.Roles = model.NewPointer("system_admin system_user")
data.Locale = model.NewPointer("zh_CN")
appErr = th.App.importUser(th.Context, &data, false)
require.Nil(t, appErr, "Should have succeeded to update valid user %v", err)
// Check user count the same.
userCountCurrent, err = th.App.Srv().Store().User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
})
require.NoError(t, err, "Failed to get user count.")
assert.Equal(t, userCount, userCountCurrent, "Unexpected number of users")
// Get the user and check all the fields are correct.
user, err2 := th.App.GetUserByUsername(username)
require.Nil(t, err2, "Failed to get user from database.")
assert.Equal(t, *data.Email, user.Email)
assert.Equal(t, *data.Nickname, user.Nickname)
assert.Equal(t, *data.FirstName, user.FirstName)
assert.Equal(t, *data.LastName, user.LastName)
assert.Equal(t, *data.Position, user.Position)
require.Equal(t, "ldap", user.AuthService, "Expected Auth Service to be ldap \"%v\"", user.AuthService)
require.Equal(t, user.AuthData, data.AuthData, "Expected AuthData to be set.")
require.Empty(t, user.Password, "Expected password to be empty.")
require.True(t, user.EmailVerified, "Expected EmailVerified to be true.")
require.Equal(t, *data.Locale, user.Locale, "Expected Locale to be the set.")
require.Equal(t, *data.Roles, user.Roles, "Expected roles to be set: %v", user.Roles)
})
t.Run("import invalid fields", func(t *testing.T) {
username := model.NewUsername()
testsDir, _ := fileutils.FindDir("tests")
data := imports.UserImportData{
Avatar: imports.Avatar{
ProfileImage: model.NewPointer(filepath.Join(testsDir, "test.png")),
},
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
Nickname: model.NewPointer(model.NewId()),
FirstName: model.NewPointer(model.NewId()),
LastName: model.NewPointer(model.NewId()),
Position: model.NewPointer(model.NewId()),
AuthData: &username,
AuthService: model.NewPointer("ldap"),
}
appErr := th.App.importUser(th.Context, &data, false)
require.Nil(t, appErr, "Should have succeeded to import valid user.")
// Check that one more user is in the DB.
userCountCurrent, err := th.App.Srv().Store().User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
})
require.NoError(t, err, "Failed to get user count.")
userCount++ // Increment the user count.
assert.Equal(t, userCount, userCountCurrent, "Unexpected number of users")
// Check Password and AuthData together.
data.Password = model.NewPointer(model.NewTestPassword())
appErr = th.App.importUser(th.Context, &data, false)
require.NotNil(t, appErr, "Should have failed to import invalid user.")
data.AuthData = nil
data.AuthService = nil
appErr = th.App.importUser(th.Context, &data, false)
require.Nil(t, appErr, "Should have succeeded to update valid user %v", err)
data.Password = model.NewPointer("")
appErr = th.App.importUser(th.Context, &data, false)
require.NotNil(t, appErr, "Should have failed to import invalid user.")
data.Password = model.NewPointer(strings.Repeat("0123456789", 10))
appErr = th.App.importUser(th.Context, &data, false)
require.NotNil(t, appErr, "Should have failed to import invalid user.")
// Check that no more user is in the DB.
userCountCurrent, err = th.App.Srv().Store().User().Count(model.UserCountOptions{
IncludeDeleted: true,
IncludeBotAccounts: false,
})
require.NoError(t, err, "Failed to get user count.")
assert.Equal(t, userCount, userCountCurrent, "Unexpected number of users")
})
t.Run("import with team and channel memberships", func(t *testing.T) {
teamName := model.NewRandomTeamName()
tAppErr := th.App.importTeam(th.Context, &imports.TeamImportData{
Name: &teamName,
DisplayName: model.NewPointer("Display Name"),
Type: model.NewPointer("O"),
}, false)
require.Nil(t, tAppErr, "Failed to import team.")
team, appErr := th.App.GetTeamByName(teamName)
require.Nil(t, appErr, "Failed to get team from database.")
channelName := model.NewId()
chanTypeOpen := model.ChannelTypeOpen
appErr = th.App.importChannel(th.Context, &imports.ChannelImportData{
Team: &teamName,
Name: &channelName,
DisplayName: model.NewPointer("Display Name"),
Type: &chanTypeOpen,
}, false)
require.Nil(t, appErr, "Failed to import channel.")
channel, appErr := th.App.GetChannelByName(th.Context, channelName, team.Id, false)
require.Nil(t, appErr, "Failed to get channel from database.")
username := model.NewUsername()
data := imports.UserImportData{
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
Nickname: model.NewPointer(model.NewId()),
FirstName: model.NewPointer(model.NewId()),
LastName: model.NewPointer(model.NewId()),
Position: model.NewPointer(model.NewId()),
}
teamMembers, appErr := th.App.GetTeamMembers(team.Id, 0, 1000, nil)
require.Nil(t, appErr, "Failed to get team member count")
teamMemberCount := len(teamMembers)
channelMemberCount, appErr := th.App.GetChannelMemberCount(th.Context, channel.Id)
require.Nil(t, appErr, "Failed to get channel member count")
t.Run("invalid team and channel memberships in dry-run mode", func(t *testing.T) {
data.Teams = &[]imports.UserTeamImportData{
{
Roles: model.NewPointer("invalid"),
Channels: &[]imports.UserChannelImportData{
{
Roles: model.NewPointer("invalid"),
},
},
},
}
appErr = th.App.importUser(th.Context, &data, true)
assert.NotNil(t, appErr)
})
t.Run("unknown team name & invalid channel membership in dry-run mode", func(t *testing.T) {
data.Teams = &[]imports.UserTeamImportData{
{
Name: model.NewPointer(model.NewId()),
Channels: &[]imports.UserChannelImportData{
{
Roles: model.NewPointer("invalid"),
},
},
},
}
appErr = th.App.importUser(th.Context, &data, true)
assert.NotNil(t, appErr)
})
t.Run("valid team & invalid channel membership in dry-run mode", func(t *testing.T) {
data.Teams = &[]imports.UserTeamImportData{
{
Name: &teamName,
Channels: &[]imports.UserChannelImportData{
{
Roles: model.NewPointer("invalid"),
},
},
},
}
appErr = th.App.importUser(th.Context, &data, true)
assert.NotNil(t, appErr)
})
t.Run("valid team & unknown channel name in dry-run mode", func(t *testing.T) {
data.Teams = &[]imports.UserTeamImportData{
{
Name: &teamName,
Channels: &[]imports.UserChannelImportData{
{
Name: model.NewPointer(model.NewId()),
},
},
},
}
appErr = th.App.importUser(th.Context, &data, true)
assert.Nil(t, appErr)
})
t.Run("valid team & valid channel name in dry-run mode", func(t *testing.T) {
data.Teams = &[]imports.UserTeamImportData{
{
Name: &teamName,
Channels: &[]imports.UserChannelImportData{
{
Name: &channelName,
},
},
},
}
appErr = th.App.importUser(th.Context, &data, true)
assert.Nil(t, appErr)
// Check no new member objects were created because dry run mode.
tmc, appErr2 := th.App.GetTeamMembers(team.Id, 0, 1000, nil)
require.Nil(t, appErr2, "Failed to get Team Member Count")
require.Len(t, tmc, teamMemberCount, "Number of team members not as expected")
cmc, appErr2 := th.App.GetChannelMemberCount(th.Context, channel.Id)
require.Nil(t, appErr2, "Failed to get Channel Member Count")
require.Equal(t, channelMemberCount, cmc, "Number of channel members not as expected")
})
t.Run("invalid team & channel membership in apply mode", func(t *testing.T) {
data.Teams = &[]imports.UserTeamImportData{
{
Roles: model.NewPointer("invalid"),
Channels: &[]imports.UserChannelImportData{
{
Roles: model.NewPointer("invalid"),
},
},
},
}
appErr = th.App.importUser(th.Context, &data, false)
assert.NotNil(t, appErr)
})
t.Run("unknown team name & invalid channel membership in apply mode", func(t *testing.T) {
data.Teams = &[]imports.UserTeamImportData{
{
Name: model.NewPointer(model.NewId()),
Channels: &[]imports.UserChannelImportData{
{
Roles: model.NewPointer("invalid"),
},
},
},
}
appErr = th.App.importUser(th.Context, &data, false)
assert.NotNil(t, appErr)
})
t.Run("import with valid team and invalid channel memberships in apply mode", func(t *testing.T) {
data.Teams = &[]imports.UserTeamImportData{
{
Name: &teamName,
Channels: &[]imports.UserChannelImportData{
{
Roles: model.NewPointer("invalid"),
},
},
},
}
appErr = th.App.importUser(th.Context, &data, false)
assert.NotNil(t, appErr)
// Check no new member objects were created because all tests should have failed so far.
tmc, appErr2 := th.App.GetTeamMembers(team.Id, 0, 1000, nil)
require.Nil(t, appErr2, "Failed to get Team Member Count")
require.Len(t, tmc, teamMemberCount)
cmc, appErr2 := th.App.GetChannelMemberCount(th.Context, channel.Id)
require.Nil(t, appErr2, "Failed to get Channel Member Count")
require.Equal(t, channelMemberCount, cmc)
})
t.Run("valid team & unknown channel name in apply mode", func(t *testing.T) {
data.Teams = &[]imports.UserTeamImportData{
{
Name: &teamName,
Channels: &[]imports.UserChannelImportData{
{
Name: model.NewPointer(model.NewId()),
},
},
},
}
appErr = th.App.importUser(th.Context, &data, false)
assert.NotNil(t, appErr)
// Check only new team member object created because dry run mode.
tmc, appErr2 := th.App.GetTeamMembers(team.Id, 0, 1000, nil)
require.Nil(t, appErr2, "Failed to get Team Member Count")
teamMemberCount++
require.Len(t, tmc, teamMemberCount)
cmc, appErr2 := th.App.GetChannelMemberCount(th.Context, channel.Id)
require.Nil(t, appErr2, "Failed to get Channel Member Count")
require.Equal(t, channelMemberCount, cmc)
// Check team member properties.
user, appErr2 := th.App.GetUserByUsername(username)
require.Nil(t, appErr2, "Failed to get user from database.")
teamMember, appErr2 := th.App.GetTeamMember(th.Context, team.Id, user.Id)
require.Nil(t, appErr2, "Failed to get team member from database.")
require.Equal(t, "team_user", teamMember.Roles)
})
t.Run("valid team & valid channel name in apply mode", func(t *testing.T) {
data.Teams = &[]imports.UserTeamImportData{
{
Name: &teamName,
Channels: &[]imports.UserChannelImportData{
{
Name: &channelName,
},
},
},
}
// convert to a new user
username = model.NewUsername()
data.Username = &username
data.Email = model.NewPointer(model.NewId() + "@example.com")
appErr2 := th.App.importUser(th.Context, &data, false)
assert.Nil(t, appErr2)
// Check only new channel member object created because dry run mode.
tmc, appErr2 := th.App.GetTeamMembers(team.Id, 0, 1000, nil)
require.Nil(t, appErr2, "Failed to get Team Member Count")
teamMemberCount++
require.Len(t, tmc, teamMemberCount, "Number of team members not as expected")
cmc, appErr2 := th.App.GetChannelMemberCount(th.Context, channel.Id)
require.Nil(t, appErr2, "Failed to get Channel Member Count")
channelMemberCount++
require.Equal(t, channelMemberCount, cmc, "Number of channel members not as expected")
user, err2 := th.App.GetUserByUsername(username)
require.Nil(t, err2, "Failed to get user from database.")
// Check channel member properties.
channelMember, appErr2 := th.App.GetChannelMember(th.Context, channel.Id, user.Id)
require.Nil(t, appErr2, "Failed to get channel member from database.")
assert.Equal(t, "channel_user", channelMember.Roles)
assert.Equal(t, "default", channelMember.NotifyProps[model.DesktopNotifyProp])
assert.Equal(t, "default", channelMember.NotifyProps[model.PushNotifyProp])
assert.Equal(t, "all", channelMember.NotifyProps[model.MarkUnreadNotifyProp])
})
t.Run("test with the properties of the team and channel membership changed", func(t *testing.T) {
data.Teams = &[]imports.UserTeamImportData{
{
Name: &teamName,
Theme: model.NewPointer(`{"awayIndicator":"#DBBD4E","buttonBg":"#23A1FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBg":"#2389d7","mentionColor":"#ffffff","mentionHighlightBg":"#fff2bb","mentionHighlightLink":"#2f81b7","newMessageSeparator":"#FF8800","onlineIndicator":"#7DBE00","sidebarBg":"#fafafa","sidebarHeaderBg":"#3481B9","sidebarHeaderTextColor":"#ffffff","sidebarText":"#333333","sidebarTextActiveBorder":"#378FD2","sidebarTextActiveColor":"#111111","sidebarTextHoverBg":"#e6f2fa","sidebarUnreadText":"#333333","type":"Mattermost"}`),
Roles: model.NewPointer("team_user team_admin"),
Channels: &[]imports.UserChannelImportData{
{
Name: &channelName,
Roles: model.NewPointer("channel_user channel_admin"),
NotifyProps: &imports.UserChannelNotifyPropsImportData{
Desktop: model.NewPointer(model.UserNotifyMention),
Mobile: model.NewPointer(model.UserNotifyMention),
MarkUnread: model.NewPointer(model.UserNotifyMention),
},
Favorite: model.NewPointer(true),
},
},
},
}
// convert to a new user
username = model.NewUsername()
data.Username = &username
data.Email = model.NewPointer(model.NewId() + "@example.com")
appErr2 := th.App.importUser(th.Context, &data, false)
assert.Nil(t, appErr2)
user, err2 := th.App.GetUserByUsername(username)
require.Nil(t, err2, "Failed to get user from database.")
// Check both member properties.
teamMember, appErr2 := th.App.GetTeamMember(th.Context, team.Id, user.Id)
require.Nil(t, appErr2, "Failed to get team member from database.")
require.Equal(t, "team_user team_admin", teamMember.Roles)
channelMember, appErr2 := th.App.GetChannelMember(th.Context, channel.Id, user.Id)
require.Nil(t, appErr2, "Failed to get channel member Desktop from database.")
assert.Equal(t, "channel_user channel_admin", channelMember.Roles)
assert.Equal(t, model.UserNotifyMention, channelMember.NotifyProps[model.DesktopNotifyProp])
assert.Equal(t, model.UserNotifyMention, channelMember.NotifyProps[model.PushNotifyProp])
assert.Equal(t, model.UserNotifyMention, channelMember.NotifyProps[model.MarkUnreadNotifyProp])
checkPreference(t, th.App, user.Id, model.PreferenceCategoryFavoriteChannel, channel.Id, "true")
checkPreference(t, th.App, user.Id, model.PreferenceCategoryTheme, team.Id, *(*data.Teams)[0].Theme)
// No more new member objects.
tmc, appErr2 := th.App.GetTeamMembers(team.Id, 0, 1000, nil)
require.Nil(t, appErr2, "Failed to get Team Member Count")
require.Len(t, tmc, teamMemberCount+1, "Number of team members not as expected")
cmc, appErr2 := th.App.GetChannelMemberCount(th.Context, channel.Id)
require.Nil(t, appErr2, "Failed to get Channel Member Count")
require.Equal(t, channelMemberCount+1, cmc, "Number of channel members not as expected")
})
})
t.Run("add a user with some preferences.", func(t *testing.T) {
teamName := model.NewRandomTeamName()
appErr2 := th.App.importTeam(th.Context, &imports.TeamImportData{
Name: &teamName,
DisplayName: model.NewPointer("Display Name"),
Type: model.NewPointer("O"),
}, false)
require.Nil(t, appErr2, "Failed to import team.")
channelName := model.NewId()
chanTypeOpen := model.ChannelTypeOpen
appErr2 = th.App.importChannel(th.Context, &imports.ChannelImportData{
Team: &teamName,
Name: &channelName,
DisplayName: model.NewPointer("Display Name"),
Type: &chanTypeOpen,
}, false)
require.Nil(t, appErr2, "Failed to import channel.")
username := model.NewUsername()
data := imports.UserImportData{
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
Theme: model.NewPointer(`{"awayIndicator":"#DCBD4E","buttonBg":"#23A2FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBg":"#2389d7","mentionColor":"#ffffff","mentionHighlightBg":"#fff2bb","mentionHighlightLink":"#2f81b7","newMessageSeparator":"#FF8800","onlineIndicator":"#7DBE00","sidebarBg":"#fafafa","sidebarHeaderBg":"#3481B9","sidebarHeaderTextColor":"#ffffff","sidebarText":"#333333","sidebarTextActiveBorder":"#378FD2","sidebarTextActiveColor":"#111111","sidebarTextHoverBg":"#e6f2fa","sidebarUnreadText":"#333333","type":"Mattermost"}`),
UseMilitaryTime: model.NewPointer("true"),
CollapsePreviews: model.NewPointer("true"),
MessageDisplay: model.NewPointer("compact"),
ColorizeUsernames: model.NewPointer("true"),
ChannelDisplayMode: model.NewPointer("centered"),
TutorialStep: model.NewPointer("3"),
UseMarkdownPreview: model.NewPointer("true"),
UseFormatting: model.NewPointer("true"),
ShowUnreadSection: model.NewPointer("true"),
EmailInterval: model.NewPointer("immediately"),
NameFormat: model.NewPointer("full_name"),
SendOnCtrlEnter: model.NewPointer("true"),
CodeBlockCtrlEnter: model.NewPointer("true"),
ShowJoinLeave: model.NewPointer("false"),
SyncDrafts: model.NewPointer("false"),
ShowUnreadScrollPosition: model.NewPointer("start_from_newest"),
LimitVisibleDmsGms: model.NewPointer("20"),
}
appErr2 = th.App.importUser(th.Context, &data, false)
assert.Nil(t, appErr2)
// Check their values.
user, appErr2 := th.App.GetUserByUsername(username)
require.Nil(t, appErr2, "Failed to get user from database.")
checkPreference(t, th.App, user.Id, model.PreferenceCategoryTheme, "", *data.Theme)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameUseMilitaryTime, *data.UseMilitaryTime)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameCollapseSetting, *data.CollapsePreviews)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameMessageDisplay, *data.MessageDisplay)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameColorizeUsernames, *data.ColorizeUsernames)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameChannelDisplayMode, *data.ChannelDisplayMode)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryTutorialSteps, user.Id, *data.TutorialStep)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryAdvancedSettings, "feature_enabled_markdown_preview", *data.UseMarkdownPreview)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryAdvancedSettings, "formatting", *data.UseFormatting)
checkPreference(t, th.App, user.Id, model.PreferenceCategorySidebarSettings, "show_unread_section", *data.ShowUnreadSection)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameNameFormat, "full_name")
checkPreference(t, th.App, user.Id, model.PreferenceCategoryAdvancedSettings, "send_on_ctrl_enter", "true")
checkPreference(t, th.App, user.Id, model.PreferenceCategoryAdvancedSettings, "code_block_ctrl_enter", "true")
checkPreference(t, th.App, user.Id, model.PreferenceCategoryAdvancedSettings, "join_leave", "false")
checkPreference(t, th.App, user.Id, model.PreferenceCategoryAdvancedSettings, "sync_drafts", "false")
checkPreference(t, th.App, user.Id, model.PreferenceCategoryAdvancedSettings, "unread_scroll_position", "start_from_newest")
checkPreference(t, th.App, user.Id, model.PreferenceCategorySidebarSettings, model.PreferenceLimitVisibleDmsGms, "20")
// Change those preferences.
data = imports.UserImportData{
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
Theme: model.NewPointer(`{"awayIndicator":"#123456","buttonBg":"#23A2FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBg":"#2389d7","mentionColor":"#ffffff","mentionHighlightBg":"#fff2bb","mentionHighlightLink":"#2f81b7","newMessageSeparator":"#FF8800","onlineIndicator":"#7DBE00","sidebarBg":"#fafafa","sidebarHeaderBg":"#3481B9","sidebarHeaderTextColor":"#ffffff","sidebarText":"#333333","sidebarTextActiveBorder":"#378FD2","sidebarTextActiveColor":"#111111","sidebarTextHoverBg":"#e6f2fa","sidebarUnreadText":"#333333","type":"Mattermost"}`),
UseMilitaryTime: model.NewPointer("false"),
CollapsePreviews: model.NewPointer("false"),
MessageDisplay: model.NewPointer("clean"),
ColorizeUsernames: model.NewPointer("false"),
ChannelDisplayMode: model.NewPointer("full"),
TutorialStep: model.NewPointer("2"),
EmailInterval: model.NewPointer("hour"),
}
appErr2 = th.App.importUser(th.Context, &data, false)
assert.Nil(t, appErr2)
// Check their values again.
checkPreference(t, th.App, user.Id, model.PreferenceCategoryTheme, "", *data.Theme)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameUseMilitaryTime, *data.UseMilitaryTime)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameCollapseSetting, *data.CollapsePreviews)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameMessageDisplay, *data.MessageDisplay)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameColorizeUsernames, *data.ColorizeUsernames)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameChannelDisplayMode, *data.ChannelDisplayMode)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryTutorialSteps, user.Id, *data.TutorialStep)
checkPreference(t, th.App, user.Id, model.PreferenceCategoryNotifications, model.PreferenceNameEmailInterval, "3600")
// Set Notify Without mention keys
data.NotifyProps = &imports.UserNotifyPropsImportData{
Desktop: model.NewPointer(model.UserNotifyAll),
DesktopSound: model.NewPointer("true"),
Email: model.NewPointer("true"),
Mobile: model.NewPointer(model.UserNotifyAll),
MobilePushStatus: model.NewPointer(model.StatusOnline),
ChannelTrigger: model.NewPointer("true"),
CommentsTrigger: model.NewPointer(model.CommentsNotifyRoot),
}
appErr2 = th.App.importUser(th.Context, &data, false)
assert.Nil(t, appErr2)
user, appErr2 = th.App.GetUserByUsername(username)
require.Nil(t, appErr2, "Failed to get user from database.")
checkNotifyProp(t, user, model.DesktopNotifyProp, model.UserNotifyAll)
checkNotifyProp(t, user, model.DesktopSoundNotifyProp, "true")
checkNotifyProp(t, user, model.EmailNotifyProp, "true")
checkNotifyProp(t, user, model.PushNotifyProp, model.UserNotifyAll)
checkNotifyProp(t, user, model.PushStatusNotifyProp, model.StatusOnline)
checkNotifyProp(t, user, model.ChannelMentionsNotifyProp, "true")
checkNotifyProp(t, user, model.CommentsNotifyProp, model.CommentsNotifyRoot)
checkNotifyProp(t, user, model.MentionKeysNotifyProp, "")
// Set Notify Props with Mention keys
data.NotifyProps = &imports.UserNotifyPropsImportData{
Desktop: model.NewPointer(model.UserNotifyAll),
DesktopSound: model.NewPointer("true"),
Email: model.NewPointer("true"),
Mobile: model.NewPointer(model.UserNotifyAll),
MobilePushStatus: model.NewPointer(model.StatusOnline),
ChannelTrigger: model.NewPointer("true"),
CommentsTrigger: model.NewPointer(model.CommentsNotifyRoot),
MentionKeys: model.NewPointer("valid,misc"),
}
appErr2 = th.App.importUser(th.Context, &data, false)
assert.Nil(t, appErr2)
user, appErr2 = th.App.GetUserByUsername(username)
require.Nil(t, appErr2, "Failed to get user from database.")
checkNotifyProp(t, user, model.DesktopNotifyProp, model.UserNotifyAll)
checkNotifyProp(t, user, model.DesktopSoundNotifyProp, "true")
checkNotifyProp(t, user, model.EmailNotifyProp, "true")
checkNotifyProp(t, user, model.PushNotifyProp, model.UserNotifyAll)
checkNotifyProp(t, user, model.PushStatusNotifyProp, model.StatusOnline)
checkNotifyProp(t, user, model.ChannelMentionsNotifyProp, "true")
checkNotifyProp(t, user, model.CommentsNotifyProp, model.CommentsNotifyRoot)
checkNotifyProp(t, user, model.MentionKeysNotifyProp, "valid,misc")
// Change Notify Props with mention keys
data.NotifyProps = &imports.UserNotifyPropsImportData{
Desktop: model.NewPointer(model.UserNotifyMention),
DesktopSound: model.NewPointer("false"),
Email: model.NewPointer("false"),
Mobile: model.NewPointer(model.UserNotifyNone),
MobilePushStatus: model.NewPointer(model.StatusAway),
ChannelTrigger: model.NewPointer("false"),
CommentsTrigger: model.NewPointer(model.CommentsNotifyAny),
MentionKeys: model.NewPointer("misc"),
}
appErr2 = th.App.importUser(th.Context, &data, false)
assert.Nil(t, appErr2)
user, appErr2 = th.App.GetUserByUsername(username)
require.Nil(t, appErr2, "Failed to get user from database.")
checkNotifyProp(t, user, model.DesktopNotifyProp, model.UserNotifyMention)
checkNotifyProp(t, user, model.DesktopSoundNotifyProp, "false")
checkNotifyProp(t, user, model.EmailNotifyProp, "false")
checkNotifyProp(t, user, model.PushNotifyProp, model.UserNotifyNone)
checkNotifyProp(t, user, model.PushStatusNotifyProp, model.StatusAway)
checkNotifyProp(t, user, model.ChannelMentionsNotifyProp, "false")
checkNotifyProp(t, user, model.CommentsNotifyProp, model.CommentsNotifyAny)
checkNotifyProp(t, user, model.MentionKeysNotifyProp, "misc")
// Change Notify Props without mention keys
data.NotifyProps = &imports.UserNotifyPropsImportData{
Desktop: model.NewPointer(model.UserNotifyMention),
DesktopSound: model.NewPointer("false"),
Email: model.NewPointer("false"),
Mobile: model.NewPointer(model.UserNotifyNone),
MobilePushStatus: model.NewPointer(model.StatusAway),
ChannelTrigger: model.NewPointer("false"),
CommentsTrigger: model.NewPointer(model.CommentsNotifyAny),
}
appErr2 = th.App.importUser(th.Context, &data, false)
assert.Nil(t, appErr2)
user, appErr2 = th.App.GetUserByUsername(username)
require.Nil(t, appErr2, "Failed to get user from database.")
checkNotifyProp(t, user, model.DesktopNotifyProp, model.UserNotifyMention)
checkNotifyProp(t, user, model.DesktopSoundNotifyProp, "false")
checkNotifyProp(t, user, model.EmailNotifyProp, "false")
checkNotifyProp(t, user, model.PushNotifyProp, model.UserNotifyNone)
checkNotifyProp(t, user, model.PushStatusNotifyProp, model.StatusAway)
checkNotifyProp(t, user, model.ChannelMentionsNotifyProp, "false")
checkNotifyProp(t, user, model.CommentsNotifyProp, model.CommentsNotifyAny)
checkNotifyProp(t, user, model.MentionKeysNotifyProp, "misc")
// Check Notify Props get set on *create* user.
username = model.NewUsername()
data = imports.UserImportData{
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
}
data.NotifyProps = &imports.UserNotifyPropsImportData{
Desktop: model.NewPointer(model.UserNotifyMention),
DesktopSound: model.NewPointer("false"),
Email: model.NewPointer("false"),
Mobile: model.NewPointer(model.UserNotifyNone),
MobilePushStatus: model.NewPointer(model.StatusAway),
ChannelTrigger: model.NewPointer("false"),
CommentsTrigger: model.NewPointer(model.CommentsNotifyAny),
MentionKeys: model.NewPointer("misc"),
}
appErr2 = th.App.importUser(th.Context, &data, false)
assert.Nil(t, appErr2)
user, appErr2 = th.App.GetUserByUsername(username)
require.Nil(t, appErr2, "Failed to get user from database.")
checkNotifyProp(t, user, model.DesktopNotifyProp, model.UserNotifyMention)
checkNotifyProp(t, user, model.DesktopSoundNotifyProp, "false")
checkNotifyProp(t, user, model.EmailNotifyProp, "false")
checkNotifyProp(t, user, model.PushNotifyProp, model.UserNotifyNone)
checkNotifyProp(t, user, model.PushStatusNotifyProp, model.StatusAway)
checkNotifyProp(t, user, model.ChannelMentionsNotifyProp, "false")
checkNotifyProp(t, user, model.CommentsNotifyProp, model.CommentsNotifyAny)
checkNotifyProp(t, user, model.MentionKeysNotifyProp, "misc")
// Test importing a user with roles set to a team and a channel which are affected by an override scheme.
// The import subsystem should translate `channel_admin/channel_user/team_admin/team_user`
// to the appropriate scheme-managed-role booleans.
// Mark the phase 2 permissions migration as completed.
err := th.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
require.NoError(t, err)
defer func() {
_, err = th.App.Srv().Store().System().PermanentDeleteByName(model.MigrationKeyAdvancedPermissionsPhase2)
require.NoError(t, err)
}()
teamSchemeData := &imports.SchemeImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
Scope: model.NewPointer("team"),
DefaultTeamGuestRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultTeamUserRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultTeamAdminRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultChannelGuestRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultChannelUserRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
DefaultChannelAdminRole: &imports.RoleImportData{
Name: model.NewPointer(model.NewId()),
DisplayName: model.NewPointer(model.NewId()),
},
Description: model.NewPointer("description"),
}
appErr2 = th.App.importScheme(th.Context, teamSchemeData, false)
assert.Nil(t, appErr2)
teamScheme, nErr := th.App.Srv().Store().Scheme().GetByName(*teamSchemeData.Name)
require.NoError(t, nErr, "Failed to import scheme")
teamData := &imports.TeamImportData{
Name: model.NewPointer(NewTestId()),
DisplayName: model.NewPointer("Display Name"),
Type: model.NewPointer("O"),
Description: model.NewPointer("The team description."),
AllowOpenInvite: model.NewPointer(true),
Scheme: &teamScheme.Name,
}
appErr2 = th.App.importTeam(th.Context, teamData, false)
assert.Nil(t, appErr2)
team, appErr2 := th.App.GetTeamByName(teamName)
require.Nil(t, appErr2, "Failed to get team from database.")
channelData := &imports.ChannelImportData{
Team: &teamName,
Name: model.NewPointer(NewTestId()),
DisplayName: model.NewPointer("Display Name"),
Type: &chanTypeOpen,
Header: model.NewPointer("Channel Header"),
Purpose: model.NewPointer("Channel Purpose"),
}
appErr2 = th.App.importChannel(th.Context, channelData, false)
assert.Nil(t, appErr2)
channel, appErr2 := th.App.GetChannelByName(th.Context, *channelData.Name, team.Id, false)
require.Nil(t, appErr2, "Failed to get channel from database")
// Test with a valid team & valid channel name in apply mode.
userData := &imports.UserImportData{
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
Teams: &[]imports.UserTeamImportData{
{
Name: &team.Name,
Roles: model.NewPointer("team_user team_admin"),
Channels: &[]imports.UserChannelImportData{
{
Name: &channel.Name,
Roles: model.NewPointer("channel_admin channel_user"),
},
},
},
},
}
appErr2 = th.App.importUser(th.Context, userData, false)
assert.Nil(t, appErr2)
user, appErr2 = th.App.GetUserByUsername(*userData.Username)
require.Nil(t, appErr2, "Failed to get user from database.")
teamMember, appErr2 := th.App.GetTeamMember(th.Context, team.Id, user.Id)
require.Nil(t, appErr2, "Failed to get the team member")
assert.True(t, teamMember.SchemeAdmin)
assert.True(t, teamMember.SchemeUser)
assert.False(t, teamMember.SchemeGuest)
assert.Equal(t, "", teamMember.ExplicitRoles)
channelMember, appErr2 := th.App.GetChannelMember(th.Context, channel.Id, user.Id)
require.Nil(t, appErr2, "Failed to get the channel member")
assert.True(t, channelMember.SchemeAdmin)
assert.True(t, channelMember.SchemeUser)
assert.False(t, channelMember.SchemeGuest)
assert.Equal(t, "", channelMember.ExplicitRoles)
// Test importing deleted user with a valid team & valid channel name in apply mode.
username = model.NewUsername()
deleteAt := model.GetMillis()
deletedUserData := &imports.UserImportData{
Username: &username,
DeleteAt: &deleteAt,
Email: model.NewPointer(model.NewId() + "@example.com"),
Teams: &[]imports.UserTeamImportData{
{
Name: &team.Name,
Roles: model.NewPointer("team_user"),
Channels: &[]imports.UserChannelImportData{
{
Name: &channel.Name,
Roles: model.NewPointer("channel_user"),
},
},
},
},
}
appErr2 = th.App.importUser(th.Context, deletedUserData, false)
assert.Nil(t, appErr2)
user, appErr2 = th.App.GetUserByUsername(*deletedUserData.Username)
require.Nil(t, appErr2, "Failed to get user from database.")
teamMember, appErr2 = th.App.GetTeamMember(th.Context, team.Id, user.Id)
require.Nil(t, appErr2, "Failed to get the team member")
assert.False(t, teamMember.SchemeAdmin)
assert.True(t, teamMember.SchemeUser)
assert.False(t, teamMember.SchemeGuest)
assert.Equal(t, "", teamMember.ExplicitRoles)
channelMember, appErr2 = th.App.GetChannelMember(th.Context, channel.Id, user.Id)
require.Nil(t, appErr2, "Failed to get the channel member")
assert.False(t, channelMember.SchemeAdmin)
assert.True(t, channelMember.SchemeUser)
assert.False(t, channelMember.SchemeGuest)
assert.Equal(t, "", channelMember.ExplicitRoles)
})
t.Run("import deleted guest with a valid team & valid channel name in apply mode", func(t *testing.T) {
teamData := &imports.TeamImportData{
Name: model.NewPointer(model.NewRandomTeamName()),
DisplayName: model.NewPointer("Display Name"),
Type: model.NewPointer("O"),
Description: model.NewPointer("The team description."),
AllowOpenInvite: model.NewPointer(true),
}
appErr := th.App.importTeam(th.Context, teamData, false)
assert.Nil(t, appErr)
team, appErr2 := th.App.GetTeamByName(*teamData.Name)
require.Nil(t, appErr2, "Failed to get team from database.")
channelData := &imports.ChannelImportData{
Team: teamData.Name,
Name: model.NewPointer(NewTestId()),
DisplayName: model.NewPointer("Display Name"),
Type: model.NewPointer(model.ChannelTypeOpen),
Header: model.NewPointer("Channel Header"),
Purpose: model.NewPointer("Channel Purpose"),
}
appErr2 = th.App.importChannel(th.Context, channelData, false)
assert.Nil(t, appErr2)
channel, appErr2 := th.App.GetChannelByName(th.Context, *channelData.Name, team.Id, false)
require.Nil(t, appErr2, "Failed to get channel from database")
username := model.NewUsername()
deleteAt := model.GetMillis()
deletedGuestData := &imports.UserImportData{
Username: &username,
DeleteAt: &deleteAt,
Email: model.NewPointer(model.NewId() + "@example.com"),
Roles: model.NewPointer("system_guest"),
Teams: &[]imports.UserTeamImportData{
{
Name: &team.Name,
Roles: model.NewPointer("team_guest"),
Channels: &[]imports.UserChannelImportData{
{
Name: &channel.Name,
Roles: model.NewPointer("channel_guest"),
},
},
},
},
}
appErr = th.App.importUser(th.Context, deletedGuestData, false)
assert.Nil(t, appErr)
user, appErr := th.App.GetUserByUsername(*deletedGuestData.Username)
require.Nil(t, appErr, "Failed to get user from database.")
teamMember, appErr := th.App.GetTeamMember(th.Context, team.Id, user.Id)
require.Nil(t, appErr, "Failed to get the team member")
assert.False(t, teamMember.SchemeAdmin)
assert.False(t, teamMember.SchemeUser)
assert.True(t, teamMember.SchemeGuest)
assert.Equal(t, "", teamMember.ExplicitRoles)
channelMember, appErr := th.App.GetChannelMember(th.Context, channel.Id, user.Id)
require.Nil(t, appErr, "Failed to get the channel member")
assert.False(t, teamMember.SchemeAdmin)
assert.False(t, channelMember.SchemeUser)
assert.True(t, teamMember.SchemeGuest)
assert.Equal(t, "", channelMember.ExplicitRoles)
})
t.Run("import guest user without any team or channel memberships", func(t *testing.T) {
username := model.NewUsername()
guestData := &imports.UserImportData{
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
Roles: model.NewPointer("system_guest"),
}
appErr := th.App.importUser(th.Context, guestData, false)
require.Nil(t, appErr, "Failed to import guest user without memberships")
user, appErr := th.App.GetUserByUsername(*guestData.Username)
require.Nil(t, appErr, "Failed to get user from database.")
assert.True(t, user.IsGuest(), "User should be a guest")
assert.Equal(t, "system_guest", user.Roles)
teams, appErr := th.App.GetTeamsForUser(user.Id)
require.Nil(t, appErr)
assert.Empty(t, teams, "Guest user should have no team memberships")
})
}
func TestImportUserTeams(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
team2 := th.CreateTeam(t)
channel2 := th.CreateChannel(t, th.BasicTeam)
channel3 := th.CreateChannel(t, team2)
customRole := th.CreateRole(t, "test_custom_role")
sampleTheme := "{\"test\":\"#abcdef\"}"
tt := []struct {
name string
data *[]imports.UserTeamImportData
expectedError bool
expectedUserTeams int
expectedUserChannels int
expectedExplicitRoles string
expectedRoles string
expectedTheme string
}{
{
name: "Not existing team should fail",
data: &[]imports.UserTeamImportData{
{
Name: model.NewPointer("not-existing-team-name"),
},
},
expectedError: true,
},
{
name: "nil data shouldn't do anything",
expectedError: false,
expectedUserTeams: 0,
expectedUserChannels: 0,
},
{
name: "Should fail if one of the roles doesn't exist",
data: &[]imports.UserTeamImportData{
{
Name: &th.BasicTeam.Name,
Roles: model.NewPointer("not-existing-role"),
},
},
expectedError: true,
expectedUserTeams: 1,
expectedUserChannels: 0,
expectedExplicitRoles: "",
expectedRoles: "team_user",
},
{
name: "Should success to import explicit role",
data: &[]imports.UserTeamImportData{
{
Name: &th.BasicTeam.Name,
Roles: &customRole.Name,
},
},
expectedError: false,
expectedUserTeams: 1,
expectedUserChannels: 1,
expectedExplicitRoles: customRole.Name,
expectedRoles: customRole.Name + " team_user",
},
{
name: "Should success to import admin role",
data: &[]imports.UserTeamImportData{
{
Name: &th.BasicTeam.Name,
Roles: model.NewPointer(model.TeamAdminRoleId),
},
},
expectedError: false,
expectedUserTeams: 1,
expectedUserChannels: 1,
expectedExplicitRoles: "",
expectedRoles: "team_user team_admin",
},
{
name: "Should success to import with theme",
data: &[]imports.UserTeamImportData{
{
Name: &th.BasicTeam.Name,
Theme: &sampleTheme,
},
},
expectedError: false,
expectedUserTeams: 1,
expectedUserChannels: 1,
expectedExplicitRoles: "",
expectedRoles: "team_user",
expectedTheme: sampleTheme,
},
{
name: "Team without channels must add the default channel",
data: &[]imports.UserTeamImportData{
{
Name: &th.BasicTeam.Name,
},
},
expectedError: false,
expectedUserTeams: 1,
expectedUserChannels: 1,
expectedExplicitRoles: "",
expectedRoles: "team_user",
},
{
name: "Team with default channel must add only the default channel",
data: &[]imports.UserTeamImportData{
{
Name: &th.BasicTeam.Name,
Channels: &[]imports.UserChannelImportData{
{
Name: model.NewPointer(model.DefaultChannelName),
},
},
},
},
expectedError: false,
expectedUserTeams: 1,
expectedUserChannels: 1,
expectedExplicitRoles: "",
expectedRoles: "team_user",
},
{
name: "Team with non default channel must add default channel and the other channel",
data: &[]imports.UserTeamImportData{
{
Name: &th.BasicTeam.Name,
Channels: &[]imports.UserChannelImportData{
{
Name: &th.BasicChannel.Name,
},
},
},
},
expectedError: false,
expectedUserTeams: 1,
expectedUserChannels: 2,
expectedExplicitRoles: "",
expectedRoles: "team_user",
},
{
name: "Multiple teams with multiple channels each",
data: &[]imports.UserTeamImportData{
{
Name: &th.BasicTeam.Name,
Channels: &[]imports.UserChannelImportData{
{
Name: &th.BasicChannel.Name,
},
{
Name: &channel2.Name,
},
},
},
{
Name: &team2.Name,
Channels: &[]imports.UserChannelImportData{
{
Name: &channel3.Name,
},
{
Name: model.NewPointer("town-square"),
},
},
},
},
expectedError: false,
expectedUserTeams: 2,
expectedUserChannels: 5,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
user := th.CreateUser(t)
// Two times import must end with the same results
for range 2 {
appErr := th.App.importUserTeams(th.Context, user, tc.data)
if tc.expectedError {
require.NotNil(t, appErr)
} else {
require.Nil(t, appErr)
}
teamMembers, nErr := th.App.Srv().Store().Team().GetTeamsForUser(th.Context, user.Id, "", true)
require.NoError(t, nErr)
require.Len(t, teamMembers, tc.expectedUserTeams)
if tc.expectedUserTeams == 1 {
require.Equal(t, tc.expectedExplicitRoles, teamMembers[0].ExplicitRoles, "Not matching expected explicit roles")
require.Equal(t, tc.expectedRoles, teamMembers[0].Roles, "not matching expected roles")
if tc.expectedTheme != "" {
pref, prefErr := th.App.Srv().Store().Preference().Get(user.Id, model.PreferenceCategoryTheme, teamMembers[0].TeamId)
require.NoError(t, prefErr)
require.Equal(t, tc.expectedTheme, pref.Value)
}
}
totalMembers := 0
for _, teamMember := range teamMembers {
channelMembers, err := th.App.Srv().Store().Channel().GetMembersForUser(teamMember.TeamId, user.Id)
require.NoError(t, err)
totalMembers += len(channelMembers)
}
require.Equal(t, tc.expectedUserChannels, totalMembers)
}
})
}
t.Run("Should fail if the MaxUserPerTeam is reached", func(t *testing.T) {
user := th.CreateUser(t)
data := &[]imports.UserTeamImportData{
{
Name: &th.BasicTeam.Name,
},
}
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 1 })
defer th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 100 })
appErr := th.App.importUserTeams(th.Context, user, data)
require.NotNil(t, appErr)
})
}
func TestImportUserChannels(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
channel2 := th.CreateChannel(t, th.BasicTeam)
customRole := th.CreateRole(t, "test_custom_role")
sampleNotifyProps := imports.UserChannelNotifyPropsImportData{
Desktop: model.NewPointer("all"),
Mobile: model.NewPointer("none"),
MarkUnread: model.NewPointer("all"),
}
tt := []struct {
name string
data *[]imports.UserChannelImportData
expectedError bool
expectedUserChannels int
expectedExplicitRoles string
expectedRoles string
expectedNotifyProps *imports.UserChannelNotifyPropsImportData
}{
{
name: "Not existing channel should fail",
data: &[]imports.UserChannelImportData{
{
Name: model.NewPointer("not-existing-channel-name"),
},
},
expectedError: true,
},
{
name: "nil data shouldn't do anything",
expectedError: false,
expectedUserChannels: 0,
},
{
name: "Should fail if one of the roles doesn't exist",
data: &[]imports.UserChannelImportData{
{
Name: &th.BasicChannel.Name,
Roles: model.NewPointer("not-existing-role"),
},
},
expectedError: true,
expectedUserChannels: 1,
expectedExplicitRoles: "",
expectedRoles: "channel_user",
},
{
name: "Should success to import explicit role",
data: &[]imports.UserChannelImportData{
{
Name: &th.BasicChannel.Name,
Roles: &customRole.Name,
},
},
expectedError: false,
expectedUserChannels: 1,
expectedExplicitRoles: customRole.Name,
expectedRoles: customRole.Name + " channel_user",
},
{
name: "Should success to import admin role",
data: &[]imports.UserChannelImportData{
{
Name: &th.BasicChannel.Name,
Roles: model.NewPointer(model.ChannelAdminRoleId),
},
},
expectedError: false,
expectedUserChannels: 1,
expectedExplicitRoles: "",
expectedRoles: "channel_user channel_admin",
},
{
name: "Should success to import with notifyProps",
data: &[]imports.UserChannelImportData{
{
Name: &th.BasicChannel.Name,
NotifyProps: &sampleNotifyProps,
},
},
expectedError: false,
expectedUserChannels: 1,
expectedExplicitRoles: "",
expectedRoles: "channel_user",
expectedNotifyProps: &sampleNotifyProps,
},
{
name: "Should import properly multiple channels",
data: &[]imports.UserChannelImportData{
{
Name: &th.BasicChannel.Name,
},
{
Name: &channel2.Name,
},
},
expectedError: false,
expectedUserChannels: 2,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
user := th.CreateUser(t)
_, _, err := th.App.ch.srv.teamService.JoinUserToTeam(th.Context, th.BasicTeam, user)
require.NoError(t, err)
// Two times import must end with the same results
for range 2 {
appErr := th.App.importUserChannels(th.Context, user, th.BasicTeam, tc.data)
if tc.expectedError {
require.NotNil(t, appErr)
} else {
require.Nil(t, appErr)
}
channelMembers, err := th.App.Srv().Store().Channel().GetMembersForUser(th.BasicTeam.Id, user.Id)
require.NoError(t, err)
require.Len(t, channelMembers, tc.expectedUserChannels)
if tc.expectedUserChannels == 1 {
channelMember := channelMembers[0]
require.Equal(t, tc.expectedExplicitRoles, channelMember.ExplicitRoles, "Not matching expected explicit roles")
require.Equal(t, tc.expectedRoles, channelMember.Roles, "not matching expected roles")
if tc.expectedNotifyProps != nil {
require.Equal(t, *tc.expectedNotifyProps.Desktop, channelMember.NotifyProps[model.DesktopNotifyProp])
require.Equal(t, *tc.expectedNotifyProps.Mobile, channelMember.NotifyProps[model.PushNotifyProp])
require.Equal(t, *tc.expectedNotifyProps.MarkUnread, channelMember.NotifyProps[model.MarkUnreadNotifyProp])
}
}
}
})
}
}
func TestImportUserDefaultNotifyProps(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
// Create a valid new user with some, but not all, notify props populated.
username := model.NewUsername()
data := imports.UserImportData{
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
NotifyProps: &imports.UserNotifyPropsImportData{
Email: model.NewPointer("false"),
MentionKeys: model.NewPointer(""),
},
}
require.Nil(t, th.App.importUser(th.Context, &data, false))
user, err := th.App.GetUserByUsername(username)
require.Nil(t, err)
// Check the value of the notify prop we specified explicitly in the import data.
val, ok := user.NotifyProps[model.EmailNotifyProp]
assert.True(t, ok)
assert.Equal(t, "false", val)
// Check all the other notify props are set to their default values.
comparisonUser := model.User{Username: user.Username}
comparisonUser.SetDefaultNotifications()
for key, expectedValue := range comparisonUser.NotifyProps {
if key == model.EmailNotifyProp {
continue
}
val, ok := user.NotifyProps[key]
assert.True(t, ok)
assert.Equal(t, expectedValue, val)
}
}
func TestImportimportMultiplePostLines(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
// Create a Team.
teamName := model.NewRandomTeamName()
appErr := th.App.importTeam(th.Context, &imports.TeamImportData{
Name: &teamName,
DisplayName: model.NewPointer("Display Name"),
Type: model.NewPointer("O"),
}, false)
require.Nil(t, appErr, "Failed to import team.")
team, err := th.App.GetTeamByName(teamName)
require.Nil(t, err, "Failed to get team from database.")
// Create a Channel.
channelName := NewTestId()
chanTypeOpen := model.ChannelTypeOpen
appErr = th.App.importChannel(th.Context, &imports.ChannelImportData{
Team: &teamName,
Name: &channelName,
DisplayName: model.NewPointer("Display Name"),
Type: &chanTypeOpen,
}, false)
require.Nil(t, appErr, "Failed to import channel.")
channel, err := th.App.GetChannelByName(th.Context, channelName, team.Id, false)
require.Nil(t, err, "Failed to get channel from database.")
// Create a user.
username := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user.")
user, err := th.App.GetUserByUsername(username)
require.Nil(t, err, "Failed to get user from database.")
username2 := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username2,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user.")
user2, err := th.App.GetUserByUsername(username2)
require.Nil(t, err, "Failed to get user from database.")
// Count the number of posts in the testing team.
require.NoError(t, th.App.Srv().Store().Post().RefreshPostStats())
initialPostCount, nErr := th.App.Srv().Store().Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: team.Id})
require.NoError(t, nErr)
createAt := model.GetMillis()
hashtagTime := createAt + 2
replyPostTime := hashtagTime + 4
replyTime := hashtagTime + 5
var assertionCount int64
t.Run("invalid post in dry run mode", func(t *testing.T) {
// Try adding an invalid post in dry run mode.
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
},
},
LineNumber: 25,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, true, true)
assert.NotNil(t, err2)
assert.Equal(t, data.LineNumber, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("valid post in dry run mode", func(t *testing.T) {
// Try adding a valid post in dry run mode.
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Hello"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, true, true)
assert.Nil(t, err2)
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("invalid post in apply mode", func(t *testing.T) {
// Try adding an invalid post in apply mode.
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 35,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.NotNil(t, err2)
assert.Equal(t, data.LineNumber, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("valid post with invalid team in apply mode", func(t *testing.T) {
// Try adding a valid post with invalid team in apply mode.
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: model.NewPointer(NewTestId()),
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 10,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.NotNil(t, err2)
// Batch will fail when searching for teams, so no specific line
// is associated with the error
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("valid post with invalid channel in apply mode", func(t *testing.T) {
// Try adding a valid post with invalid channel in apply mode.
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: model.NewPointer(NewTestId()),
User: &username,
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 7,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.NotNil(t, err2)
// Batch will fail when searching for channels, so no specific
// line is associated with the error
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("valid post with invalid user in apply mode", func(t *testing.T) {
// Try adding a valid post with invalid user in apply mode.
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: model.NewPointer(model.NewId()),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 2,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.NotNil(t, err2)
// Batch will fail when searching for users, so no specific line
// is associated with the error
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("valid post in apply mode", func(t *testing.T) {
// Try adding a valid post in apply mode.
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message"),
CreateAt: &createAt,
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err2)
assert.Equal(t, 0, errLine)
assertionCount++
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, createAt)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id
require.False(t, postBool, "Post properties not as expected")
// Update the post.
data = imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message"),
CreateAt: &createAt,
},
},
LineNumber: 1,
}
errLine, err = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err)
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Check the post values.
posts, nErr = th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, createAt)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post = posts[0]
postBool = post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id
require.False(t, postBool, "Post properties not as expected")
})
t.Run("import the post with a different time", func(t *testing.T) {
// Save the post with a different time.
newTime := createAt + 1
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message"),
CreateAt: &newTime,
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err2)
assert.Equal(t, 0, errLine)
assertionCount++
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
})
t.Run("import the post with a different message", func(t *testing.T) {
// Save the post with a different message.
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message 2"),
CreateAt: &createAt,
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err2)
assert.Equal(t, 0, errLine)
assertionCount++
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
})
t.Run("import post with hashtags", func(t *testing.T) {
// Test with hashtags
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message 2 #hashtagmashupcity"),
CreateAt: &hashtagTime,
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err2)
assert.Equal(t, 0, errLine)
assertionCount++
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, hashtagTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id
require.False(t, postBool, "Post properties not as expected")
require.Equal(t, "#hashtagmashupcity", post.Hashtags, "Hashtags not as expected: %s", post.Hashtags)
})
t.Run("import post with flags", func(t *testing.T) {
// Post with flags.
flagsTime := hashtagTime + 1
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message with Favorites"),
CreateAt: &flagsTime,
FlaggedBy: &[]string{
username,
username2,
},
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err2, "Expected success.")
assert.Equal(t, 0, errLine)
assertionCount++
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, flagsTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id
require.False(t, postBool, "Post properties not as expected")
checkPreference(t, th.App, user.Id, model.PreferenceCategoryFlaggedPost, post.Id, "true")
checkPreference(t, th.App, user2.Id, model.PreferenceCategoryFlaggedPost, post.Id, "true")
})
t.Run("import new post with reactions", func(t *testing.T) {
// Post with reaction.
reactionPostTime := hashtagTime + 2
reactionTime := hashtagTime + 3
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message with reactions"),
CreateAt: &reactionPostTime,
Reactions: &[]imports.ReactionImportData{{
User: &user2.Username,
EmojiName: model.NewPointer("+1"),
CreateAt: &reactionTime,
}, {
User: &user.Username,
EmojiName: model.NewPointer("+1"),
CreateAt: &reactionTime,
}},
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err2, "Expected success.")
assert.Equal(t, 0, errLine)
assertionCount++
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, reactionPostTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id || !post.HasReactions
require.False(t, postBool, "Post properties not as expected")
reactions, nErr := th.App.Srv().Store().Reaction().GetForPost(post.Id, false)
require.NoError(t, nErr, "Can't get reaction")
require.Len(t, reactions, 2, "Invalid number of reactions")
// Update post with replies with reactions.
newReactionTime := reactionTime + 1
newReplyTime := reactionPostTime + 1
data = imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message with reactions"),
CreateAt: &reactionPostTime,
Replies: &[]imports.ReplyImportData{{
User: &username,
Message: model.NewPointer("Message reply"),
CreateAt: &newReplyTime,
Reactions: &[]imports.ReactionImportData{{
User: &user2.Username,
EmojiName: model.NewPointer("+1"),
CreateAt: &newReactionTime,
}},
}},
},
},
LineNumber: 1,
}
errLine, err = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err, "Expected success.")
assert.Equal(t, 0, errLine)
// No new post created, only the reply is added.
assertionCount++
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Check the post values.
posts, nErr = th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, newReplyTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post = posts[0]
reactions, nErr = th.App.Srv().Store().Reaction().GetForPost(post.Id, false)
require.NoError(t, nErr, "Can't get reaction")
require.Len(t, reactions, 1, "Invalid number of reactions")
})
t.Run("import post with reactions with new replies", func(t *testing.T) {
// Post with reaction.
reactionPostTime := hashtagTime + 11
reactionTime := hashtagTime + 12
newReplyTime := reactionPostTime + 1
newReactionTime := reactionTime + 1
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message with reaction"),
CreateAt: &reactionPostTime,
Reactions: &[]imports.ReactionImportData{{
User: &user2.Username,
EmojiName: model.NewPointer("+1"),
CreateAt: &reactionTime,
}},
Replies: &[]imports.ReplyImportData{{
User: &username,
Message: model.NewPointer("Message reply"),
CreateAt: &newReplyTime,
Reactions: &[]imports.ReactionImportData{{
User: &user2.Username,
EmojiName: model.NewPointer("+1"),
CreateAt: &newReactionTime,
}},
}},
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err2, "Expected success.")
assert.Equal(t, 0, errLine)
assertionCount += 2
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, reactionPostTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id || !post.HasReactions
require.False(t, postBool, "Post properties not as expected")
reactions, nErr := th.App.Srv().Store().Reaction().GetForPost(post.Id, false)
require.NoError(t, nErr, "Can't get reaction")
require.Len(t, reactions, 1, "Invalid number of reactions")
// Check the post values.
posts, nErr = th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, newReplyTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post = posts[0]
reactions, nErr = th.App.Srv().Store().Reaction().GetForPost(post.Id, false)
require.NoError(t, nErr, "Can't get reaction")
require.Len(t, reactions, 1, "Invalid number of reactions")
})
t.Run("import post with replies", func(t *testing.T) {
// Post with reply.
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message with reply"),
CreateAt: &replyPostTime,
Replies: &[]imports.ReplyImportData{{
User: &user2.Username,
Message: model.NewPointer("Message reply"),
CreateAt: &replyTime,
Props: &model.StringInterface{"key": "value"},
}},
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err2, "Expected success.")
assert.Equal(t, 0, errLine)
assertionCount += 2
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, replyPostTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id
require.False(t, postBool, "Post properties not as expected")
// Check the reply values.
replies, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, replyTime)
require.NoError(t, nErr)
require.Len(t, replies, 1, "Unexpected number of posts found.")
reply := replies[0]
replyBool := reply.Message != *(*data.Post.Replies)[0].Message || reply.CreateAt != *(*data.Post.Replies)[0].CreateAt || reply.UserId != user2.Id
require.False(t, replyBool, "Post properties not as expected")
v := reply.GetProp("key")
require.NotNil(t, v, "Post prop should exist")
require.Equal(t, "value", v, "Post props not as expected")
require.Equal(t, post.Id, reply.RootId, "Unexpected reply RootId")
})
t.Run("update post with replies", func(t *testing.T) {
replyPostTime2 := replyPostTime + 1
// Create post without replies.
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user2.Username,
Message: model.NewPointer("Message with reply"),
CreateAt: &replyPostTime2,
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err2, "Expected success.")
assert.Equal(t, 0, errLine)
assertionCount++
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Update post with replies.
data = imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user2.Username,
Message: model.NewPointer("Message with reply"),
CreateAt: &replyPostTime2,
Replies: &[]imports.ReplyImportData{{
User: &username,
Message: model.NewPointer("Message reply"),
CreateAt: &replyTime,
}},
},
},
LineNumber: 1,
}
errLine, err = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err, "Expected success.")
assert.Equal(t, 0, errLine)
// No new post created, only the reply is added.
assertionCount++
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Create new post with replies based on the previous one.
data = imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user2.Username,
Message: model.NewPointer("Message with reply 2"),
CreateAt: &replyPostTime2,
Replies: &[]imports.ReplyImportData{{
User: &username,
Message: model.NewPointer("Message reply"),
CreateAt: &replyTime,
}},
},
},
LineNumber: 1,
}
errLine, err = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err, "Expected success.")
assert.Equal(t, 0, errLine)
assertionCount += 2
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Create new reply for existing post with replies.
data = imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user2.Username,
Message: model.NewPointer("Message with reply"),
CreateAt: &replyPostTime2,
Replies: &[]imports.ReplyImportData{{
User: &username,
Message: model.NewPointer("Message reply 2"),
CreateAt: &replyTime,
}},
},
},
LineNumber: 1,
}
errLine, err = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err, "Expected success.")
assert.Equal(t, 0, errLine)
assertionCount++
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Create new reply with type and edit_at for existing post with replies.
editedReplyPostTime := hashtagTime + 6
editedReplyTime := hashtagTime + 7
editedReplyEditTime := hashtagTime + 8
data = imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user2.Username,
Message: model.NewPointer("Message with reply"),
CreateAt: &editedReplyPostTime,
Replies: &[]imports.ReplyImportData{{
User: &username,
Type: model.NewPointer(model.PostTypeSystemGeneric),
Message: model.NewPointer("Message reply 3"),
CreateAt: &editedReplyTime,
EditAt: &editedReplyEditTime,
}},
},
},
LineNumber: 1,
}
errLine, err = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err, "Expected success.")
assert.Equal(t, 0, errLine)
assertionCount += 2
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
// Check the reply values.
replies, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, editedReplyTime)
assert.NoError(t, nErr, "Expected success.")
reply := replies[0]
importReply := (*data.Post.Replies)[0]
replyBool := reply.Type != *importReply.Type || reply.Message != *importReply.Message || reply.CreateAt != *importReply.CreateAt || reply.EditAt != *importReply.EditAt || reply.UserId != user.Id
require.False(t, replyBool, "Post properties not as expected")
})
t.Run("import post with pinned message", func(t *testing.T) {
// Create another Team.
teamName2 := model.NewRandomTeamName()
appErr := th.App.importTeam(th.Context, &imports.TeamImportData{
Name: &teamName2,
DisplayName: model.NewPointer("Display Name 2"),
Type: model.NewPointer("O"),
}, false)
require.Nil(t, appErr, "Failed to import team.")
team2, err2 := th.App.GetTeamByName(teamName2)
require.Nil(t, err2, "Failed to get team from database.")
// Create another Channel for the another team.
appErr = th.App.importChannel(th.Context, &imports.ChannelImportData{
Team: &teamName2,
Name: &channelName,
DisplayName: model.NewPointer("Display Name"),
Type: &chanTypeOpen,
}, false)
require.Nil(t, appErr, "Failed to import channel.")
_, err = th.App.GetChannelByName(th.Context, channelName, team2.Id, false)
require.Nil(t, err, "Failed to get channel from database.")
// Count the number of posts in the team2.
require.NoError(t, th.App.Srv().Store().Post().RefreshPostStats())
initialPostCountForTeam2, nErr := th.App.Srv().Store().Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: team2.Id})
require.NoError(t, nErr)
// Try adding two valid posts in apply mode.
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("another message"),
CreateAt: &createAt,
},
},
LineNumber: 1,
}
data2 := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName2,
Channel: &channelName,
User: &username,
Message: model.NewPointer("another message"),
CreateAt: &createAt,
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data, data2}, false, true)
assert.Nil(t, err2)
assert.Equal(t, 0, errLine)
// Create a pinned message.
data = imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user2.Username,
Message: model.NewPointer("Pinned Message"),
CreateAt: model.NewPointer(model.GetMillis()),
IsPinned: model.NewPointer(true),
},
},
LineNumber: 1,
}
errLine, err = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
resultPosts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, *data.Post.CreateAt)
require.NoError(t, nErr, "Expected success.")
// Should be one post only created at this time.
require.Equal(t, 1, len(resultPosts))
resultPost := resultPosts[0]
require.True(t, resultPost.IsPinned, "This post should be pinned.")
// Posts should be added to the right team
AssertAllPostsCount(t, th.App, initialPostCountForTeam2, 1, team2.Id)
assertionCount += 2
AssertAllPostsCount(t, th.App, initialPostCount, assertionCount, team.Id)
})
t.Run("Importing a post with a reply both pinned", func(t *testing.T) {
// Create a thread.
importCreate := time.Now().Add(-1 * time.Minute).UnixMilli()
replyCreate := time.Now().Add(-30 * time.Second).UnixMilli()
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user.Username,
Message: model.NewPointer("Thread Message"),
CreateAt: model.NewPointer(importCreate),
IsPinned: model.NewPointer(true),
Replies: &[]imports.ReplyImportData{{
User: &user.Username,
Message: model.NewPointer("Reply"),
CreateAt: model.NewPointer(replyCreate),
IsPinned: model.NewPointer(true),
}},
},
},
LineNumber: 1,
}
_, err = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
resultPosts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, importCreate)
require.NoError(t, nErr)
require.Equal(t, 1, len(resultPosts))
require.True(t, resultPosts[0].IsPinned)
resultPosts, nErr = th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, replyCreate)
require.NoError(t, nErr)
require.Equal(t, 1, len(resultPosts))
require.True(t, resultPosts[0].IsPinned)
})
t.Run("Importing a post with a thread", func(t *testing.T) {
// Create a thread.
importCreate := time.Now().Add(-1 * time.Minute).UnixMilli()
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user.Username,
Message: model.NewPointer("Thread Message"),
CreateAt: model.NewPointer(importCreate),
Replies: &[]imports.ReplyImportData{{
User: &user.Username,
Message: model.NewPointer("Reply"),
CreateAt: model.NewPointer(model.GetMillis()),
}},
ThreadFollowers: &[]imports.ThreadFollowerImportData{{
User: &user.Username,
LastViewed: model.NewPointer(model.GetMillis()),
}, {
User: &user2.Username,
LastViewed: model.NewPointer(model.GetMillis()),
}},
},
},
LineNumber: 1,
}
errLine, err2 := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err2)
require.Equal(t, 0, errLine)
resultPosts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, importCreate)
require.NoError(t, nErr)
require.Equal(t, 1, len(resultPosts))
followers, nErr := th.App.Srv().Store().Thread().GetThreadFollowers(resultPosts[0].Id, true)
require.NoError(t, nErr)
assert.ElementsMatch(t, []string{user.Id, user2.Id}, followers)
})
t.Run("Importing a post with a non existent follower", func(t *testing.T) {
// Create a thread.
importCreate := time.Now().Add(-1 * time.Minute).UnixMilli()
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user.Username,
Message: model.NewPointer("Thread Message"),
CreateAt: model.NewPointer(importCreate),
Replies: &[]imports.ReplyImportData{{
User: &user.Username,
Message: model.NewPointer("Reply"),
CreateAt: model.NewPointer(model.GetMillis()),
}},
ThreadFollowers: &[]imports.ThreadFollowerImportData{{
User: &user.Username,
LastViewed: model.NewPointer(model.GetMillis()),
}, {
User: model.NewPointer("invalid.user"),
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.NotNil(t, err)
require.Equal(t, 1, errLine)
})
t.Run("Importing a post with a non existent follower", func(t *testing.T) {
importCreate := time.Now().Add(-1 * time.Minute).UnixMilli()
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user.Username,
Message: model.NewPointer("Thread Message"),
CreateAt: model.NewPointer(importCreate),
Replies: &[]imports.ReplyImportData{{
User: &user.Username,
Message: model.NewPointer("Reply"),
CreateAt: model.NewPointer(model.GetMillis()),
}},
ThreadFollowers: &[]imports.ThreadFollowerImportData{{
User: &user.Username,
LastViewed: model.NewPointer(model.GetMillis()),
}, {
User: model.NewPointer("invalid.user"),
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.NotNil(t, err)
require.Equal(t, 1, errLine)
})
t.Run("Importing a post with new followers", func(t *testing.T) {
importCreate := time.Now().Add(-5 * time.Minute).UnixMilli()
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Hello"),
CreateAt: model.NewPointer(importCreate),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
resultPosts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, importCreate)
require.NoError(t, nErr)
require.Equal(t, 1, len(resultPosts))
data = imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user.Username,
Message: model.NewPointer("Hello"),
CreateAt: model.NewPointer(importCreate),
Replies: &[]imports.ReplyImportData{{
User: &user.Username,
Message: model.NewPointer("Reply"),
CreateAt: model.NewPointer(model.GetMillis()),
}},
ThreadFollowers: &[]imports.ThreadFollowerImportData{{
User: &user.Username,
LastViewed: model.NewPointer(model.GetMillis()),
}},
},
},
LineNumber: 1,
}
errLine, err = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
followers, nErr := th.App.Srv().Store().Thread().GetThreadFollowers(resultPosts[0].Id, true)
require.NoError(t, nErr)
assert.ElementsMatch(t, []string{user.Id}, followers)
})
t.Run("Importing a post that someone flagged", func(t *testing.T) {
// Create a thread.
importCreate := time.Now().Add(-1 * time.Minute).UnixMilli()
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user.Username,
Message: model.NewPointer("Flagged Message"),
CreateAt: model.NewPointer(importCreate),
FlaggedBy: &[]string{user.Username},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
resultPosts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, importCreate)
require.NoError(t, nErr)
require.Equal(t, 1, len(resultPosts))
pref, nErr := th.App.ch.srv.Store().Preference().GetCategoryAndName(model.PreferenceCategoryFlaggedPost, resultPosts[0].Id)
require.NoError(t, nErr)
require.Len(t, pref, 1)
assert.Equal(t, user.Id, pref[0].UserId)
})
t.Run("Importing a post that someone flagged its replies", func(t *testing.T) {
// Create a thread.
importCreate := time.Now().Add(-1 * time.Minute).UnixMilli()
replyCreate := time.Now().Add(-30 * time.Second).UnixMilli()
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user.Username,
Message: model.NewPointer("Flagged Message"),
CreateAt: model.NewPointer(importCreate),
Replies: &[]imports.ReplyImportData{{
User: &user.Username,
Message: model.NewPointer("Reply"),
CreateAt: model.NewPointer(replyCreate),
FlaggedBy: &[]string{user2.Username},
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
resultPosts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, replyCreate)
require.NoError(t, nErr)
require.Equal(t, 1, len(resultPosts))
pref, nErr := th.App.ch.srv.Store().Preference().GetCategoryAndName(model.PreferenceCategoryFlaggedPost, resultPosts[0].Id)
require.NoError(t, nErr)
require.Len(t, pref, 1)
assert.Equal(t, user2.Id, pref[0].UserId)
})
}
func TestImportImportPost(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
// Create a Team.
teamName := model.NewRandomTeamName()
appErr := th.App.importTeam(th.Context, &imports.TeamImportData{
Name: &teamName,
DisplayName: model.NewPointer("Display Name"),
Type: model.NewPointer("O"),
}, false)
require.Nil(t, appErr, "Failed to import team.")
team, appErr := th.App.GetTeamByName(teamName)
require.Nil(t, appErr, "Failed to get team from database.")
// Create a Channel.
channelName := NewTestId()
chanTypeOpen := model.ChannelTypeOpen
appErr = th.App.importChannel(th.Context, &imports.ChannelImportData{
Team: &teamName,
Name: &channelName,
DisplayName: model.NewPointer("Display Name"),
Type: &chanTypeOpen,
}, false)
require.Nil(t, appErr, "Failed to import channel.")
channel, appErr := th.App.GetChannelByName(th.Context, channelName, team.Id, false)
require.Nil(t, appErr, "Failed to get channel from database.")
// Create a user.
username := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user.")
user, appErr := th.App.GetUserByUsername(username)
require.Nil(t, appErr, "Failed to get user from database.")
username2 := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username2,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user.")
user2, appErr := th.App.GetUserByUsername(username2)
require.Nil(t, appErr, "Failed to get user from database.")
// Count the number of posts in the testing team.
require.NoError(t, th.App.Srv().Store().Post().RefreshPostStats())
initialPostCount, nErr := th.App.Srv().Store().Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: team.Id})
require.NoError(t, nErr)
time := model.GetMillis()
hashtagTime := time + 2
replyPostTime := hashtagTime + 4
replyTime := hashtagTime + 5
posttypeTime := hashtagTime + 6
editatCreateTime := hashtagTime + 7
editatEditTime := hashtagTime + 8
t.Run("Try adding an invalid post in dry run mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
},
},
LineNumber: 12,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, true, true)
assert.NotNil(t, err)
assert.Equal(t, data.LineNumber, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("Try adding a valid post in dry run mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Hello"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, true, true)
assert.Nil(t, err)
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("Try adding an invalid post in apply mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 2,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.NotNil(t, err)
assert.Equal(t, data.LineNumber, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("Try adding a valid post with invalid team in apply mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: model.NewPointer(NewTestId()),
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 7,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.NotNil(t, err)
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("Try adding a valid post with invalid channel in apply mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: model.NewPointer(NewTestId()),
User: &username,
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 8,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.NotNil(t, err)
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("Try adding a valid post with invalid user in apply mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: model.NewPointer(model.NewId()),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 9,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.NotNil(t, err)
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id)
})
t.Run("Try adding a valid post in apply mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message"),
CreateAt: &time,
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err)
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 1, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, time)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id
require.False(t, postBool, "Post properties not as expected")
})
t.Run("Update the post", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username2,
Message: model.NewPointer("Message"),
CreateAt: &time,
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err)
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 1, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, time)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user2.Id
require.False(t, postBool, "Post properties not as expected")
})
t.Run("Save the post with a different time", func(t *testing.T) {
newTime := time + 1
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message"),
CreateAt: &newTime,
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err)
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 2, team.Id)
})
t.Run("Save the post with a different message", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message 2"),
CreateAt: &time,
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err)
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 3, team.Id)
})
t.Run("Test with hashtag", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message 2 #hashtagmashupcity"),
CreateAt: &hashtagTime,
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
assert.Nil(t, err)
assert.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 4, team.Id)
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, hashtagTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id
require.False(t, postBool, "Post properties not as expected")
require.Equal(t, "#hashtagmashupcity", post.Hashtags, "Hashtags not as expected: %s", post.Hashtags)
})
t.Run("Post with flags", func(t *testing.T) {
flagsTime := hashtagTime + 1
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message with Favorites"),
CreateAt: &flagsTime,
FlaggedBy: &[]string{
username,
username2,
},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 5, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, flagsTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id
require.False(t, postBool, "Post properties not as expected")
checkPreference(t, th.App, user.Id, model.PreferenceCategoryFlaggedPost, post.Id, "true")
checkPreference(t, th.App, user2.Id, model.PreferenceCategoryFlaggedPost, post.Id, "true")
})
t.Run("Post with reaction", func(t *testing.T) {
reactionPostTime := hashtagTime + 2
reactionTime := hashtagTime + 3
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message with reaction"),
CreateAt: &reactionPostTime,
Reactions: &[]imports.ReactionImportData{{
User: &user2.Username,
EmojiName: model.NewPointer("+1"),
CreateAt: &reactionTime,
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 6, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, reactionPostTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id || !post.HasReactions
require.False(t, postBool, "Post properties not as expected")
reactions, nErr := th.App.Srv().Store().Reaction().GetForPost(post.Id, false)
require.NoError(t, nErr, "Can't get reaction")
require.Len(t, reactions, 1, "Invalid number of reactions")
})
t.Run("Post with reply", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message with reply"),
CreateAt: &replyPostTime,
Replies: &[]imports.ReplyImportData{{
User: &user2.Username,
Message: model.NewPointer("Message reply"),
CreateAt: &replyTime,
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 8, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, replyPostTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id
require.False(t, postBool, "Post properties not as expected")
// Check the reply values.
replies, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, replyTime)
require.NoError(t, nErr)
require.Len(t, replies, 1, "Unexpected number of posts found.")
reply := replies[0]
replyBool := reply.Message != *(*data.Post.Replies)[0].Message || reply.CreateAt != *(*data.Post.Replies)[0].CreateAt || reply.UserId != user2.Id
require.False(t, replyBool, "Post properties not as expected")
require.Equal(t, post.Id, reply.RootId, "Unexpected reply RootId")
})
t.Run("Update post with replies", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user2.Username,
Message: model.NewPointer("Message with reply"),
CreateAt: &replyPostTime,
Replies: &[]imports.ReplyImportData{{
User: &username,
Message: model.NewPointer("Message reply"),
CreateAt: &replyTime,
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 8, team.Id)
})
t.Run("Create new post with replies based on the previous one", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user2.Username,
Message: model.NewPointer("Message with reply 2"),
CreateAt: &replyPostTime,
Replies: &[]imports.ReplyImportData{{
User: &username,
Message: model.NewPointer("Message reply"),
CreateAt: &replyTime,
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 10, team.Id)
})
t.Run("Create new reply for existing post with replies", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user2.Username,
Message: model.NewPointer("Message with reply"),
CreateAt: &replyPostTime,
Replies: &[]imports.ReplyImportData{{
User: &username,
Message: model.NewPointer("Message reply 2"),
CreateAt: &replyTime,
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 11, team.Id)
})
t.Run("Post with Type", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Type: model.NewPointer(model.PostTypeSystemGeneric),
Message: model.NewPointer("Message with Type"),
CreateAt: &posttypeTime,
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 12, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, posttypeTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id || post.Type != *data.Post.Type
require.False(t, postBool, "Post properties not as expected")
})
t.Run("Post with EditAt", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: model.NewPointer("Message with Type"),
CreateAt: &editatCreateTime,
EditAt: &editatEditTime,
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 13, team.Id)
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, editatCreateTime)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.Post.Message || post.CreateAt != *data.Post.CreateAt || post.UserId != user.Id || post.EditAt != *data.Post.EditAt
require.False(t, postBool, "Post properties not as expected")
})
t.Run("Reply CreateAt before parent post CreateAt", func(t *testing.T) {
now := model.GetMillis()
before := now - 10
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &user2.Username,
Message: model.NewPointer("Message with reply"),
CreateAt: &now,
Replies: &[]imports.ReplyImportData{{
User: &username,
Message: model.NewPointer("Message reply 2"),
CreateAt: &before,
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, now)
require.NoError(t, nErr)
require.Len(t, posts, 2, "Unexpected number of posts found.")
require.NoError(t, th.TestLogger.Flush())
testlib.AssertLog(t, th.LogBuffer, mlog.LvlWarn.Name, "Reply CreateAt is before parent post CreateAt, setting it to parent post CreateAt")
rootPost := posts[0]
replyPost := posts[1]
if rootPost.RootId != "" {
replyPost = posts[0]
rootPost = posts[1]
}
require.Equal(t, rootPost.Id, replyPost.RootId)
require.Equal(t, now, replyPost.CreateAt)
})
}
func TestImportImportDirectChannel(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
user3 := th.CreateUser(t)
// Check how many channels are in the database.
directChannelCount, err := th.App.Srv().Store().Channel().AnalyticsTypeCount("", model.ChannelTypeDirect)
require.NoError(t, err, "Failed to get direct channel count.")
groupChannelCount, err := th.App.Srv().Store().Channel().AnalyticsTypeCount("", model.ChannelTypeGroup)
require.NoError(t, err, "Failed to get group channel count.")
// We need to generate the dataset twice to test the same data with different formats.
generateDataset := func(data imports.DirectChannelImportData) map[string]imports.DirectChannelImportData {
members := make([]string, len(data.Participants))
for i, member := range data.Participants {
members[i] = *member.Username
}
return map[string]imports.DirectChannelImportData{
"Participants": data,
"Members": {
Members: &members,
},
}
}
t.Run("Invalid channel in dry-run mode", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(model.NewId()),
},
},
Header: model.NewPointer("Channel Header"),
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
err = th.App.importDirectChannel(th.Context, &data, true)
require.Error(t, err)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
})
}
})
t.Run("Valid DIRECT channel with a nonexistent member in dry-run mode", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(model.NewId()),
},
{
Username: model.NewPointer(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, true)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
})
}
})
t.Run("Valid GROUP channel with a nonexistent member in dry-run mode", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(model.NewId()),
},
{
Username: model.NewPointer(model.NewId()),
},
{
Username: model.NewPointer(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, true)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
})
}
})
t.Run("Invalid channel in apply mode", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
err = th.App.importDirectChannel(th.Context, &data, false)
require.Error(t, err)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
})
}
})
t.Run("Valid DIRECT channel ", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(th.BasicUser.Username),
},
{
Username: model.NewPointer(th.BasicUser2.Username),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that one more DIRECT channel is in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Do the same DIRECT channel again.
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Update the channel's HEADER
data.Header = model.NewPointer("New Channel Header 2")
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Get the channel to check that the header was updated.
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
require.Equal(t, channel.Header, *data.Header)
})
}
})
t.Run("GROUP channel with an extra invalid member", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(th.BasicUser.Username),
},
{
Username: model.NewPointer(th.BasicUser2.Username),
},
{
Username: model.NewPointer(user3.Username),
},
{
Username: model.NewPointer(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.NotNil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
})
}
})
t.Run("Valid GROUP channel", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(th.BasicUser.Username),
},
{
Username: model.NewPointer(th.BasicUser2.Username),
},
{
Username: model.NewPointer(user3.Username),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that one more GROUP channel is in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
// Do the same DIRECT channel again.
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
// Update the channel's HEADER
data.Header = model.NewPointer("New Channel Header 3")
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
// Get the channel to check that the header was updated.
userIDs := []string{
th.BasicUser.Id,
th.BasicUser2.Id,
user3.Id,
}
channel, appErr := th.App.createGroupChannel(th.Context, userIDs, th.BasicUser.Id)
require.Equal(t, appErr.Id, store.ChannelExistsError)
require.Equal(t, channel.Header, *data.Header)
})
}
})
t.Run("Import a channel with some favorites", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(th.BasicUser.Username),
},
{
Username: model.NewPointer(th.BasicUser2.Username),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
data.FavoritedBy = &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryFavoriteChannel, channel.Id, "true")
checkPreference(t, th.App, th.BasicUser2.Id, model.PreferenceCategoryFavoriteChannel, channel.Id, "true")
})
}
})
t.Run("Import a DM channel and user last view should be imported", func(t *testing.T) {
lastView := model.GetMillis()
data := imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(th.BasicUser.Username),
LastViewedAt: model.NewPointer(lastView),
},
{
Username: model.NewPointer(th.BasicUser2.Username),
},
},
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
members, appErr := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 100)
require.Nil(t, appErr)
require.Len(t, members, 2)
for _, member := range members {
if member.UserId == th.BasicUser.Id {
require.Equal(t, member.LastViewedAt, lastView)
}
}
})
t.Run("Import a DM channel and preserve if the channel was shown to users", func(t *testing.T) {
data := imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(th.BasicUser.Username),
},
{
Username: model.NewPointer(th.BasicUser2.Username),
},
},
ShownBy: &[]string{
th.BasicUser.Username,
},
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
members, appErr := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 100)
require.Nil(t, appErr)
require.Len(t, members, 2)
for _, member := range members {
if member.UserId == th.BasicUser.Id {
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryDirectChannelShow, th.BasicUser2.Id, "true")
}
}
})
t.Run("Import a GM channel and preserve if the channel was shown to users", func(t *testing.T) {
data := imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(th.BasicUser.Username),
},
{
Username: model.NewPointer(th.BasicUser2.Username),
},
{
Username: model.NewPointer(user3.Username),
},
},
ShownBy: &[]string{
th.BasicUser.Username,
},
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetGroupChannel(th.Context, []string{th.BasicUser.Id, th.BasicUser2.Id, user3.Id})
require.Nil(t, appErr)
members, appErr := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 100)
require.Nil(t, appErr)
require.Len(t, members, 3)
for _, member := range members {
if member.UserId == th.BasicUser.Id {
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryGroupChannelShow, channel.Id, "true")
}
}
})
}
func TestImportImportDirectPost(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
// Create the DIRECT channel.
channelData := imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(th.BasicUser.Username),
},
{
Username: model.NewPointer(th.BasicUser2.Username),
},
},
}
appErr := th.App.importDirectChannel(th.Context, &channelData, false)
require.Nil(t, appErr)
// Get the channel.
var directChannel *model.Channel
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
require.NotEmpty(t, channel)
directChannel = channel
// Get the number of posts in the system.
require.NoError(t, th.App.Srv().Store().Post().RefreshPostStats())
result, err := th.App.Srv().Store().Post().AnalyticsPostCount(&model.PostCountOptions{})
require.NoError(t, err)
initialPostCount := result
initialDate := model.GetMillis()
posttypeDate := initialDate + 3
editatCreateDate := initialDate + 4
editatEditDate := initialDate + 5
t.Run("Try adding an invalid post in dry run mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 7,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, true, true)
require.NotNil(t, err)
require.Equal(t, data.LineNumber, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, "")
})
t.Run("Try adding a valid post in dry run mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, true, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, "")
})
t.Run("Try adding an invalid post in apply mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
model.NewId(),
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 9,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.NotNil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, "")
})
t.Run("Try adding a valid post in apply mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(initialDate),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 1, "")
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(directChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1)
post := posts[0]
require.Equal(t, post.Message, *data.DirectPost.Message)
require.Equal(t, post.CreateAt, *data.DirectPost.CreateAt)
require.Equal(t, post.UserId, th.BasicUser.Id)
})
t.Run("Import the post again", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(initialDate),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 1, "")
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(directChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1)
post := posts[0]
require.Equal(t, post.Message, *data.DirectPost.Message)
require.Equal(t, post.CreateAt, *data.DirectPost.CreateAt)
require.Equal(t, post.UserId, th.BasicUser.Id)
})
t.Run("Save the post with a different time", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(initialDate + 1),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 2, "")
})
t.Run("Save the post with a different message", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message 2"),
CreateAt: model.NewPointer(initialDate + 1),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 3, "")
})
t.Run("Test with hashtag", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message 2 #hashtagmashupcity"),
CreateAt: model.NewPointer(initialDate + 2),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 4, "")
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(directChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1)
post := posts[0]
require.Equal(t, post.Message, *data.DirectPost.Message)
require.Equal(t, post.CreateAt, *data.DirectPost.CreateAt)
require.Equal(t, post.UserId, th.BasicUser.Id)
require.Equal(t, post.Hashtags, "#hashtagmashupcity")
})
t.Run("Test with some flags", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
FlaggedBy: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 5, "")
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(directChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1)
post := posts[0]
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryFlaggedPost, post.Id, "true")
checkPreference(t, th.App, th.BasicUser2.Id, model.PreferenceCategoryFlaggedPost, post.Id, "true")
})
t.Run("Test with Type", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Type: model.NewPointer(model.PostTypeSystemGeneric),
Message: model.NewPointer("Message with Type"),
CreateAt: model.NewPointer(posttypeDate),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 6, "")
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(directChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1)
post := posts[0]
assert.Equal(t, post.Type, *data.DirectPost.Type)
assert.Equal(t, post.Message, *data.DirectPost.Message)
assert.Equal(t, post.CreateAt, *data.DirectPost.CreateAt)
assert.Equal(t, post.UserId, th.BasicUser.Id)
})
t.Run("Test with EditAt", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message with EditAt"),
CreateAt: model.NewPointer(editatCreateDate),
EditAt: model.NewPointer(editatEditDate),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 7, "")
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(directChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1)
post := posts[0]
assert.Equal(t, post.Message, *data.DirectPost.Message)
assert.Equal(t, post.CreateAt, *data.DirectPost.CreateAt)
assert.Equal(t, post.EditAt, *data.DirectPost.EditAt)
assert.Equal(t, post.UserId, th.BasicUser.Id)
})
t.Run("Test with IsPinned", func(t *testing.T) {
pinnedValue := true
creationTime := model.GetMillis()
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message with EditAt"),
CreateAt: &creationTime,
IsPinned: &pinnedValue,
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 8, "")
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(directChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1)
post := posts[0]
require.True(t, post.IsPinned)
})
t.Run("Importing a direct post with a thread", func(t *testing.T) {
// Create a thread.
importCreate := time.Now().Add(-1 * time.Minute).UnixMilli()
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Thread Message"),
CreateAt: model.NewPointer(importCreate),
Replies: &[]imports.ReplyImportData{{
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Reply"),
CreateAt: model.NewPointer(model.GetMillis()),
}},
ThreadFollowers: &[]imports.ThreadFollowerImportData{{
User: model.NewPointer(th.BasicUser.Username),
LastViewed: model.NewPointer(model.GetMillis()),
}, {
User: model.NewPointer(th.BasicUser2.Username),
LastViewed: model.NewPointer(model.GetMillis()),
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
resultPosts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, importCreate)
require.NoError(t, nErr)
require.Equal(t, 1, len(resultPosts))
followers, nErr := th.App.Srv().Store().Thread().GetThreadFollowers(resultPosts[0].Id, true)
require.NoError(t, nErr)
assert.ElementsMatch(t, []string{th.BasicUser.Id, th.BasicUser2.Id}, followers)
})
t.Run("Importing a direct post with new followers", func(t *testing.T) {
importCreate := time.Now().Add(-5 * time.Minute).UnixMilli()
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Hello"),
CreateAt: model.NewPointer(importCreate),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
resultPosts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, importCreate)
require.NoError(t, nErr)
require.Equal(t, 1, len(resultPosts))
data = imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Hello"),
CreateAt: model.NewPointer(importCreate),
Replies: &[]imports.ReplyImportData{{
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Reply"),
CreateAt: model.NewPointer(model.GetMillis()),
}},
ThreadFollowers: &[]imports.ThreadFollowerImportData{{
User: model.NewPointer(th.BasicUser.Username),
LastViewed: model.NewPointer(model.GetMillis()),
}},
},
},
LineNumber: 1,
}
errLine, err = th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
followers, nErr := th.App.Srv().Store().Thread().GetThreadFollowers(resultPosts[0].Id, true)
require.NoError(t, nErr)
assert.ElementsMatch(t, []string{th.BasicUser.Id}, followers)
})
// ------------------ Group Channel -------------------------
// Create the GROUP channel.
user3 := th.CreateUser(t)
channelData = imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewPointer(th.BasicUser.Username),
},
{
Username: model.NewPointer(th.BasicUser2.Username),
},
{
Username: model.NewPointer(user3.Username),
},
},
}
appErr = th.App.importDirectChannel(th.Context, &channelData, false)
require.Nil(t, appErr)
// Get the channel.
var groupChannel *model.Channel
userIDs := []string{
th.BasicUser.Id,
th.BasicUser2.Id,
user3.Id,
}
channel, appErr = th.App.createGroupChannel(th.Context, userIDs, th.BasicUser.Id)
require.Equal(t, appErr.Id, store.ChannelExistsError)
groupChannel = channel
// Get the number of posts in the system.
require.NoError(t, th.App.Srv().Store().Post().RefreshPostStats())
result, nErr := th.App.Srv().Store().Post().AnalyticsPostCount(&model.PostCountOptions{})
require.NoError(t, nErr)
initialPostCount = result
t.Run("Try adding an invalid post in dry run mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser.Username),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 4,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, true, true)
require.NotNil(t, err)
require.Equal(t, data.LineNumber, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, "")
})
t.Run("Try adding a valid post in dry run mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, true, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, "")
})
t.Run("Try adding an invalid post in apply mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
model.NewId(),
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 8,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.NotNil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 0, "")
})
t.Run("Try adding a valid post in apply mode", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(initialDate + 10),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 1, "")
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(groupChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1)
post := posts[0]
require.Equal(t, post.Message, *data.DirectPost.Message)
require.Equal(t, post.CreateAt, *data.DirectPost.CreateAt)
require.Equal(t, post.UserId, th.BasicUser.Id)
})
t.Run("Import the post again", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(initialDate + 10),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 1, "")
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(groupChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1)
post := posts[0]
require.Equal(t, post.Message, *data.DirectPost.Message)
require.Equal(t, post.CreateAt, *data.DirectPost.CreateAt)
require.Equal(t, post.UserId, th.BasicUser.Id)
})
t.Run("Save the post with a different time", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(initialDate + 11),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 2, "")
})
t.Run("Save the post with a different message", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message 2"),
CreateAt: model.NewPointer(initialDate + 11),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 3, "")
})
t.Run("Test with hashtag", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message 2 #hashtagmashupcity"),
CreateAt: model.NewPointer(initialDate + 12),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 4, "")
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(groupChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1)
post := posts[0]
require.Equal(t, post.Message, *data.DirectPost.Message)
require.Equal(t, post.CreateAt, *data.DirectPost.CreateAt)
require.Equal(t, post.UserId, th.BasicUser.Id)
require.Equal(t, post.Hashtags, "#hashtagmashupcity")
})
t.Run("Test with some flags", func(t *testing.T) {
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
FlaggedBy: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message"),
CreateAt: model.NewPointer(model.GetMillis()),
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 5, "")
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(groupChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1)
post := posts[0]
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryFlaggedPost, post.Id, "true")
checkPreference(t, th.App, th.BasicUser2.Id, model.PreferenceCategoryFlaggedPost, post.Id, "true")
})
t.Run("Post with reaction", func(t *testing.T) {
reactionPostTime := model.NewPointer(initialDate + 22)
reactionTime := model.NewPointer(initialDate + 23)
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message with reaction"),
CreateAt: reactionPostTime,
Reactions: &[]imports.ReactionImportData{{
User: model.NewPointer(th.BasicUser2.Username),
EmojiName: model.NewPointer("+1"),
CreateAt: reactionTime,
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 6, "")
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(groupChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.DirectPost.Message || post.CreateAt != *data.DirectPost.CreateAt || post.UserId != th.BasicUser.Id || !post.HasReactions
require.False(t, postBool, "Post properties not as expected")
reactions, nErr := th.App.Srv().Store().Reaction().GetForPost(post.Id, false)
require.NoError(t, nErr, "Can't get reaction")
require.Len(t, reactions, 1, "Invalid number of reactions")
})
t.Run("Post with reply", func(t *testing.T) {
replyPostTime := model.NewPointer(initialDate + 25)
replyTime := model.NewPointer(initialDate + 26)
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message with reply"),
CreateAt: replyPostTime,
Replies: &[]imports.ReplyImportData{{
User: model.NewPointer(th.BasicUser2.Username),
Message: model.NewPointer("Message reply"),
CreateAt: replyTime,
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 8, "")
// Check the post values.
posts, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(groupChannel.Id, *data.DirectPost.CreateAt)
require.NoError(t, nErr)
require.Len(t, posts, 1, "Unexpected number of posts found.")
post := posts[0]
postBool := post.Message != *data.DirectPost.Message || post.CreateAt != *data.DirectPost.CreateAt || post.UserId != th.BasicUser.Id
require.False(t, postBool, "Post properties not as expected")
// Check the reply values.
replies, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, *replyTime)
require.NoError(t, nErr)
require.Len(t, replies, 1, "Unexpected number of posts found.")
reply := replies[0]
replyBool := reply.Message != *(*data.DirectPost.Replies)[0].Message || reply.CreateAt != *(*data.DirectPost.Replies)[0].CreateAt || reply.UserId != th.BasicUser2.Id
require.False(t, replyBool, "Post properties not as expected")
require.Equal(t, post.Id, reply.RootId, "Unexpected reply RootId")
})
t.Run("Update post with replies", func(t *testing.T) {
replyPostTime := model.NewPointer(initialDate + 25)
replyTime := model.NewPointer(initialDate + 26)
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser2.Username),
Message: model.NewPointer("Message with reply"),
CreateAt: replyPostTime,
Replies: &[]imports.ReplyImportData{{
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message reply"),
CreateAt: replyTime,
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 8, "")
})
t.Run("Create new post with replies based on the previous one", func(t *testing.T) {
replyPostTime := model.NewPointer(initialDate + 27)
replyTime := model.NewPointer(initialDate + 28)
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser2.Username),
Message: model.NewPointer("Message with reply 2"),
CreateAt: replyPostTime,
Replies: &[]imports.ReplyImportData{{
User: model.NewPointer(th.BasicUser.Username),
Message: model.NewPointer("Message reply"),
CreateAt: replyTime,
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 10, "")
})
t.Run("Post with reply having non-empty type and edit_at", func(t *testing.T) {
replyPostTime := model.NewPointer(initialDate + 29)
replyTime := model.NewPointer(initialDate + 30)
replyEditTime := model.NewPointer(initialDate + 31)
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
},
User: model.NewPointer(th.BasicUser2.Username),
Message: model.NewPointer("Message with reply"),
CreateAt: replyPostTime,
Replies: &[]imports.ReplyImportData{{
User: model.NewPointer(th.BasicUser.Username),
Type: model.NewPointer(model.PostTypeSystemGeneric),
Message: model.NewPointer("Message reply 2"),
CreateAt: replyTime,
EditAt: replyEditTime,
}},
},
},
LineNumber: 1,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
AssertAllPostsCount(t, th.App, initialPostCount, 12, "")
// Check the reply values.
replies, nErr := th.App.Srv().Store().Post().GetPostsCreatedAt(channel.Id, *replyTime)
require.NoError(t, nErr)
require.Len(t, replies, 1, "Unexpected number of posts found.")
reply := replies[0]
importReply := (*data.DirectPost.Replies)[0]
replyBool := reply.Type != *importReply.Type || reply.Message != *importReply.Message || reply.CreateAt != *importReply.CreateAt || reply.EditAt != *importReply.EditAt || reply.UserId != th.BasicUser.Id
require.False(t, replyBool, "Post properties not as expected")
})
}
func TestImportImportEmoji(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = true })
testsDir, _ := fileutils.FindDir("tests")
testImage := filepath.Join(testsDir, "test.png")
data := imports.EmojiImportData{Name: model.NewPointer(model.NewId())}
appErr := th.App.importEmoji(th.Context, &data, true)
assert.NotNil(t, appErr, "Invalid emoji should have failed dry run")
emoji, nErr := th.App.Srv().Store().Emoji().GetByName(th.Context, *data.Name, true)
assert.Nil(t, emoji, "Emoji should not have been imported")
assert.Error(t, nErr)
data.Image = model.NewPointer(testImage)
appErr = th.App.importEmoji(th.Context, &data, true)
assert.Nil(t, appErr, "Valid emoji should have passed dry run")
data = imports.EmojiImportData{Name: model.NewPointer(model.NewId())}
appErr = th.App.importEmoji(th.Context, &data, false)
assert.NotNil(t, appErr, "Invalid emoji should have failed apply mode")
data.Image = model.NewPointer("non-existent-file")
appErr = th.App.importEmoji(th.Context, &data, false)
assert.NotNil(t, appErr, "Emoji with bad image file should have failed apply mode")
data.Image = model.NewPointer(testImage)
appErr = th.App.importEmoji(th.Context, &data, false)
assert.Nil(t, appErr, "Valid emoji should have succeeded apply mode")
emoji, nErr = th.App.Srv().Store().Emoji().GetByName(th.Context, *data.Name, true)
assert.NotNil(t, emoji, "Emoji should have been imported")
assert.NoError(t, nErr, "Emoji should have been imported without any error")
appErr = th.App.importEmoji(th.Context, &data, false)
assert.Nil(t, appErr, "Second run should have succeeded apply mode")
data = imports.EmojiImportData{Name: model.NewPointer("smiley"), Image: model.NewPointer(testImage)}
appErr = th.App.importEmoji(th.Context, &data, false)
assert.Nil(t, appErr, "System emoji should not fail")
largeImage := filepath.Join(testsDir, "large_image_file.jpg")
data = imports.EmojiImportData{Name: model.NewPointer(model.NewId()), Image: model.NewPointer(largeImage)}
appErr = th.App.importEmoji(th.Context, &data, false)
require.NotNil(t, appErr)
require.ErrorIs(t, appErr.Unwrap(), utils.ErrSizeLimitExceeded)
}
func TestImportAttachment(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
testsDir, _ := fileutils.FindDir("tests")
testImage := filepath.Join(testsDir, "test.png")
invalidPath := "some-invalid-path"
userID := model.NewId()
data := imports.AttachmentImportData{Path: &testImage}
_, err := th.App.importAttachment(th.Context, &data, &model.Post{UserId: userID, ChannelId: "some-channel"}, "some-team", true)
assert.Nil(t, err, "sample run without errors")
attachments := GetAttachments(userID, th, t)
assert.Len(t, attachments, 1)
data = imports.AttachmentImportData{Path: &invalidPath}
_, err = th.App.importAttachment(th.Context, &data, &model.Post{UserId: model.NewId(), ChannelId: "some-channel"}, "some-team", true)
assert.NotNil(t, err, "should have failed when opening the file")
assert.Equal(t, err.Id, "app.import.attachment.bad_file.error")
}
func TestImportPostAndRepliesWithAttachments(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
// Create a Team.
teamName := model.NewRandomTeamName()
appErr := th.App.importTeam(th.Context, &imports.TeamImportData{
Name: &teamName,
DisplayName: model.NewPointer("Display Name"),
Type: model.NewPointer("O"),
}, false)
require.Nil(t, appErr, "Failed to import team.")
team, appErr := th.App.GetTeamByName(teamName)
require.Nil(t, appErr, "Failed to get team from database.")
// Create a Channel.
channelName := NewTestId()
chanTypeOpen := model.ChannelTypeOpen
appErr = th.App.importChannel(th.Context, &imports.ChannelImportData{
Team: &teamName,
Name: &channelName,
DisplayName: model.NewPointer("Display Name"),
Type: &chanTypeOpen,
}, false)
require.Nil(t, appErr, "Failed to import channel.")
_, appErr = th.App.GetChannelByName(th.Context, channelName, team.Id, false)
require.Nil(t, appErr, "Failed to get channel from database.")
// Create a user3.
username := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user.")
user3, appErr := th.App.GetUserByUsername(username)
require.Nil(t, appErr, "Failed to get user3 from database.")
require.NotNil(t, user3)
username2 := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username2,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user2.")
user2, appErr := th.App.GetUserByUsername(username2)
require.Nil(t, appErr, "Failed to get user2 from database.")
// Create direct post users.
username3 := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username3,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user3.")
user3, appErr = th.App.GetUserByUsername(username3)
require.Nil(t, appErr, "Failed to get user3 from database.")
username4 := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username4,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user4.")
user4, appErr := th.App.GetUserByUsername(username4)
require.Nil(t, appErr, "Failed to get user4 from database.")
// Post with attachments
time := model.GetMillis()
attachmentsPostTime := time
attachmentsReplyTime := time + 1
testsDir, _ := fileutils.FindDir("tests")
testImage := filepath.Join(testsDir, "test.png")
testMarkDown := filepath.Join(testsDir, "test-attachments.md")
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username3,
Message: model.NewPointer("Message with reply"),
CreateAt: &attachmentsPostTime,
Attachments: &[]imports.AttachmentImportData{{Path: &testImage}, {Path: &testMarkDown}},
Replies: &[]imports.ReplyImportData{{
User: &user4.Username,
Message: model.NewPointer("Message reply"),
CreateAt: &attachmentsReplyTime,
Attachments: &[]imports.AttachmentImportData{{Path: &testImage}},
}},
},
},
LineNumber: 19,
}
t.Run("import with attachment", func(t *testing.T) {
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
attachments := GetAttachments(user3.Id, th, t)
require.Len(t, attachments, 2)
assert.Contains(t, attachments[0].Path, team.Id)
assert.Contains(t, attachments[1].Path, team.Id)
AssertFileIdsInPost(attachments, th, t)
attachments = GetAttachments(user4.Id, th, t)
require.Len(t, attachments, 1)
assert.Contains(t, attachments[0].Path, team.Id)
AssertFileIdsInPost(attachments, th, t)
})
t.Run("import existing post with new attachment", func(t *testing.T) {
data.Post.Attachments = &[]imports.AttachmentImportData{{Path: &testImage}}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
attachments := GetAttachments(user3.Id, th, t)
require.Len(t, attachments, 1)
assert.Contains(t, attachments[0].Path, team.Id)
AssertFileIdsInPost(attachments, th, t)
attachments = GetAttachments(user4.Id, th, t)
require.Len(t, attachments, 1)
assert.Contains(t, attachments[0].Path, team.Id)
AssertFileIdsInPost(attachments, th, t)
})
t.Run("Reply with Attachments in Direct Post", func(t *testing.T) {
directImportData := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
user3.Username,
user2.Username,
},
User: &user3.Username,
Message: model.NewPointer("Message with Replies"),
CreateAt: model.NewPointer(model.GetMillis()),
Replies: &[]imports.ReplyImportData{{
User: &user2.Username,
Message: model.NewPointer("Message reply with attachment"),
CreateAt: model.NewPointer(model.GetMillis()),
Attachments: &[]imports.AttachmentImportData{{Path: &testImage}},
}},
},
},
LineNumber: 7,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{directImportData}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
attachments := GetAttachments(user2.Id, th, t)
require.Len(t, attachments, 1)
assert.Contains(t, attachments[0].Path, "noteam")
AssertFileIdsInPost(attachments, th, t)
})
t.Run("import existing post with different attachment's content", func(t *testing.T) {
tmpDir := os.TempDir()
filePath := filepath.Join(tmpDir, "test_diff.png")
t.Run("different size", func(t *testing.T) {
testImage := filepath.Join(testsDir, "test.png")
imageData, err := os.ReadFile(testImage)
require.NoError(t, err)
err = os.WriteFile(filePath, imageData, 0644)
require.NoError(t, err)
data.Post.Attachments = &[]imports.AttachmentImportData{{Path: &filePath}}
data.Post.Replies = nil
data.Post.Message = model.NewPointer("new post")
errLine, appErr := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, appErr)
require.Equal(t, 0, errLine)
attachments := GetAttachments(user3.Id, th, t)
require.Len(t, attachments, 2)
assert.Contains(t, attachments[1].Path, team.Id)
AssertFileIdsInPost(attachments[1:], th, t)
testImage = filepath.Join(testsDir, "test-data-graph.png")
imageData, err = os.ReadFile(testImage)
require.NoError(t, err)
err = os.WriteFile(filePath, imageData, 0644)
require.NoError(t, err)
data.Post.Attachments = &[]imports.AttachmentImportData{{Path: &filePath}}
data.Post.Replies = nil
errLine, appErr = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, appErr)
require.Equal(t, 0, errLine)
attachments2 := GetAttachments(user3.Id, th, t)
require.NotEqual(t, attachments, attachments2)
require.Len(t, attachments2, 2)
assert.Contains(t, attachments2[1].Path, team.Id)
AssertFileIdsInPost(attachments2[1:], th, t)
})
t.Run("same size", func(t *testing.T) {
imageData, err := os.ReadFile(filepath.Join(testsDir, "test_img_diff_A.png"))
require.NoError(t, err)
err = os.WriteFile(filePath, imageData, 0644)
require.NoError(t, err)
data.Post.Attachments = &[]imports.AttachmentImportData{{Path: &filePath}}
data.Post.Replies = nil
data.Post.Message = model.NewPointer("new post2")
errLine, appErr := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, appErr)
require.Equal(t, 0, errLine)
attachments := GetAttachments(user3.Id, th, t)
require.Len(t, attachments, 3)
assert.Contains(t, attachments[2].Path, team.Id)
AssertFileIdsInPost(attachments[2:], th, t)
imageData, err = os.ReadFile(filepath.Join(testsDir, "test_img_diff_B.png"))
require.NoError(t, err)
err = os.WriteFile(filePath, imageData, 0644)
require.NoError(t, err)
data.Post.Attachments = &[]imports.AttachmentImportData{{Path: &filePath}}
data.Post.Replies = nil
errLine, appErr = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, appErr)
require.Equal(t, 0, errLine)
attachments2 := GetAttachments(user3.Id, th, t)
require.NotEqual(t, attachments, attachments2)
require.Len(t, attachments2, 3)
assert.Contains(t, attachments2[2].Path, team.Id)
AssertFileIdsInPost(attachments2[2:], th, t)
})
})
}
func TestImportDirectPostWithAttachments(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
testsDir, _ := fileutils.FindDir("tests")
testImage := filepath.Join(testsDir, "test.png")
testImage2 := filepath.Join(testsDir, "test.svg")
// create a temp file with same name as original but with a different first byte
tmpFolder, err := os.MkdirTemp("", "imgFake")
require.NoError(t, err)
testImageFake := filepath.Join(tmpFolder, "test.png")
fakeFileData, err := os.ReadFile(testImage)
require.NoError(t, err)
fakeFileData[0] = 0
err = os.WriteFile(testImageFake, fakeFileData, 0644)
require.NoError(t, err)
defer func() {
err := os.RemoveAll(tmpFolder)
require.NoError(t, err)
}()
// Create a user.
username := model.NewUsername()
appErr := th.App.importUser(th.Context, &imports.UserImportData{
Username: &username,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user.")
user1, appErr := th.App.GetUserByUsername(username)
require.Nil(t, appErr, "Failed to get user1 from database.")
username2 := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username2,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user2.")
user2, appErr := th.App.GetUserByUsername(username2)
require.Nil(t, appErr, "Failed to get user2 from database.")
directImportData := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
user1.Username,
user2.Username,
},
User: &user1.Username,
Message: model.NewPointer("Direct message"),
CreateAt: model.NewPointer(model.GetMillis()),
Attachments: &[]imports.AttachmentImportData{{Path: &testImage}},
},
},
LineNumber: 3,
}
t.Run("Regular import of attachment", func(t *testing.T) {
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{directImportData}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
attachments := GetAttachments(user1.Id, th, t)
require.Len(t, attachments, 1)
assert.Contains(t, attachments[0].Path, "noteam")
AssertFileIdsInPost(attachments, th, t)
})
t.Run("Attempt to import again with same file entirely, should NOT add an attachment", func(t *testing.T) {
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{directImportData}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
attachments := GetAttachments(user1.Id, th, t)
require.Len(t, attachments, 1)
})
t.Run("Attempt to import again with same name and size but different content, SHOULD add an attachment", func(t *testing.T) {
directImportDataFake := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
user1.Username,
user2.Username,
},
User: &user1.Username,
Message: model.NewPointer("Direct message"),
CreateAt: model.NewPointer(model.GetMillis()),
Attachments: &[]imports.AttachmentImportData{{Path: &testImageFake}},
},
},
LineNumber: 2,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{directImportDataFake}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
attachments := GetAttachments(user1.Id, th, t)
require.Len(t, attachments, 2)
})
t.Run("Attempt to import again with same data, SHOULD add an attachment, since it's different name", func(t *testing.T) {
directImportData2 := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
user1.Username,
user2.Username,
},
User: &user1.Username,
Message: model.NewPointer("Direct message"),
CreateAt: model.NewPointer(model.GetMillis()),
Attachments: &[]imports.AttachmentImportData{{Path: &testImage2}},
},
},
LineNumber: 2,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{directImportData2}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
attachments := GetAttachments(user1.Id, th, t)
require.Len(t, attachments, 3)
})
}
func TestZippedImportPostAndRepliesWithAttachments(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
// Create a Team.
teamName := model.NewRandomTeamName()
appErr := th.App.importTeam(th.Context, &imports.TeamImportData{
Name: &teamName,
DisplayName: model.NewPointer("Display Name"),
Type: model.NewPointer("O"),
}, false)
require.Nil(t, appErr, "Failed to import team.")
team, appErr := th.App.GetTeamByName(teamName)
require.Nil(t, appErr, "Failed to get team from database.")
// Create a Channel.
channelName := NewTestId()
chanTypeOpen := model.ChannelTypeOpen
appErr = th.App.importChannel(th.Context, &imports.ChannelImportData{
Team: &teamName,
Name: &channelName,
DisplayName: model.NewPointer("Display Name"),
Type: &chanTypeOpen,
}, false)
require.Nil(t, appErr, "Failed to import channel.")
_, appErr = th.App.GetChannelByName(th.Context, channelName, team.Id, false)
require.Nil(t, appErr, "Failed to get channel from database.")
// Create users
username2 := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username2,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user2.")
user2, appErr := th.App.GetUserByUsername(username2)
require.Nil(t, appErr, "Failed to get user2 from database.")
// Create direct post users.
username3 := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username3,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user3.")
user3, appErr := th.App.GetUserByUsername(username3)
require.Nil(t, appErr, "Failed to get user3 from database.")
username4 := model.NewUsername()
appErr = th.App.importUser(th.Context, &imports.UserImportData{
Username: &username4,
Email: model.NewPointer(model.NewId() + "@example.com"),
}, false)
require.Nil(t, appErr, "Failed to import user4.")
user4, appErr := th.App.GetUserByUsername(username4)
require.Nil(t, appErr, "Failed to get user4 from database.")
// Post with attachments
time := model.GetMillis()
attachmentsPostTime := time
attachmentsReplyTime := time + 1
testsDir, _ := fileutils.FindDir("tests")
testImage := filepath.Join(testsDir, "test.png")
testZipFileName := filepath.Join(testsDir, "import_test.zip")
testZip, err := os.Open(testZipFileName)
require.NoError(t, err, "failed to open test zip")
fi, err := testZip.Stat()
require.NoError(t, err, "failed to get file info")
testZipReader, err := zip.NewReader(testZip, fi.Size())
require.NoError(t, err, "failed to read test zip")
require.NotEmpty(t, testZipReader.File)
imageData := testZipReader.File[0]
testMarkDown := filepath.Join(testsDir, "test-attachments.md")
data := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
Post: &imports.PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username3,
Message: model.NewPointer("Message with reply"),
CreateAt: &attachmentsPostTime,
Attachments: &[]imports.AttachmentImportData{{Path: &testImage}, {Path: &testMarkDown}},
Replies: &[]imports.ReplyImportData{{
User: &user4.Username,
Message: model.NewPointer("Message reply"),
CreateAt: &attachmentsReplyTime,
Attachments: &[]imports.AttachmentImportData{{Path: &testImage, Data: imageData}},
}},
},
},
LineNumber: 19,
}
t.Run("import with attachment", func(t *testing.T) {
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
attachments := GetAttachments(user3.Id, th, t)
require.Len(t, attachments, 2)
assert.Contains(t, attachments[0].Path, team.Id)
assert.Contains(t, attachments[1].Path, team.Id)
AssertFileIdsInPost(attachments, th, t)
attachments = GetAttachments(user4.Id, th, t)
require.Len(t, attachments, 1)
assert.Contains(t, attachments[0].Path, team.Id)
AssertFileIdsInPost(attachments, th, t)
})
t.Run("import existing post with new attachment", func(t *testing.T) {
data.Post.Attachments = &[]imports.AttachmentImportData{{Path: &testImage}}
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
attachments := GetAttachments(user3.Id, th, t)
require.Len(t, attachments, 1)
assert.Contains(t, attachments[0].Path, team.Id)
AssertFileIdsInPost(attachments, th, t)
attachments = GetAttachments(user4.Id, th, t)
require.Len(t, attachments, 1)
assert.Contains(t, attachments[0].Path, team.Id)
AssertFileIdsInPost(attachments, th, t)
})
t.Run("Reply with Attachments in Direct Post", func(t *testing.T) {
directImportData := imports.LineImportWorkerData{
LineImportData: imports.LineImportData{
DirectPost: &imports.DirectPostImportData{
ChannelMembers: &[]string{
user3.Username,
user2.Username,
},
User: &user3.Username,
Message: model.NewPointer("Message with Replies"),
CreateAt: model.NewPointer(model.GetMillis()),
Replies: &[]imports.ReplyImportData{{
User: &user2.Username,
Message: model.NewPointer("Message reply with attachment"),
CreateAt: model.NewPointer(model.GetMillis()),
Attachments: &[]imports.AttachmentImportData{{Path: &testImage}},
}},
},
},
LineNumber: 7,
}
errLine, err := th.App.importMultipleDirectPostLines(th.Context, []imports.LineImportWorkerData{directImportData}, false, true)
require.Nil(t, err, "Expected success.")
require.Equal(t, 0, errLine)
attachments := GetAttachments(user2.Id, th, t)
require.Len(t, attachments, 1)
assert.Contains(t, attachments[0].Path, "noteam")
AssertFileIdsInPost(attachments, th, t)
})
t.Run("import existing post with different attachment's content", func(t *testing.T) {
var fileA, fileB *zip.File
for _, f := range testZipReader.File {
if f.Name == "data/test_img_diff_A.png" {
fileA = f
} else if f.Name == "data/test_img_diff_B.png" {
fileB = f
}
}
require.NotNil(t, fileA)
require.NotNil(t, fileB)
data.Post.Attachments = &[]imports.AttachmentImportData{{Path: &fileA.Name, Data: fileA}}
data.Post.Message = model.NewPointer("new post")
data.Post.Replies = nil
errLine, err := th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
attachments := GetAttachments(user3.Id, th, t)
require.Len(t, attachments, 2)
assert.Contains(t, attachments[1].Path, team.Id)
AssertFileIdsInPost(attachments[1:], th, t)
fileB.Name = fileA.Name
data.Post.Attachments = &[]imports.AttachmentImportData{{Path: &fileA.Name, Data: fileB}}
errLine, err = th.App.importMultiplePostLines(th.Context, []imports.LineImportWorkerData{data}, false, true)
require.Nil(t, err)
require.Equal(t, 0, errLine)
attachments = GetAttachments(user3.Id, th, t)
require.Len(t, attachments, 2)
assert.Contains(t, attachments[1].Path, team.Id)
AssertFileIdsInPost(attachments[1:], th, t)
})
}
func TestCompareFilesContent(t *testing.T) {
mainHelper.Parallel(t)
t.Run("empty", func(t *testing.T) {
ok, err := compareFilesContent(strings.NewReader(""), strings.NewReader(""), 0)
require.NoError(t, err)
require.True(t, ok)
})
t.Run("no match", func(t *testing.T) {
ok, err := compareFilesContent(strings.NewReader("fileA"), strings.NewReader("fileB"), 0)
require.NoError(t, err)
require.False(t, ok)
})
t.Run("match", func(t *testing.T) {
ok, err := compareFilesContent(strings.NewReader("fileA"), strings.NewReader("fileA"), 0)
require.NoError(t, err)
require.True(t, ok)
})
}
func BenchmarkCompareFilesContent(b *testing.B) {
tmpDir := os.TempDir()
fileAPath := filepath.Join(tmpDir, "fileA")
fileBPath := filepath.Join(tmpDir, "fileB")
fileA, err := os.Create(fileAPath)
require.NoError(b, err)
defer func() {
err = fileA.Close()
require.NoError(b, err)
err = os.Remove(fileAPath)
require.NoError(b, err)
}()
fileB, err := os.Create(fileBPath)
require.NoError(b, err)
defer func() {
err = fileB.Close()
require.NoError(b, err)
err = os.Remove(fileBPath)
require.NoError(b, err)
}()
fileSize := int64(1024 * 1024 * 1024) // 1GB
err = fileA.Truncate(fileSize)
require.NoError(b, err)
err = fileB.Truncate(fileSize)
require.NoError(b, err)
bufSizesMap := map[string]int64{
"32KB": 1024 * 32, // current default of io.Copy
"128KB": 1024 * 128,
"1MB": 1024 * 1024,
"2MB": 1024 * 1024 * 2,
"4MB": 1024 * 1024 * 4,
"8MB": 1024 * 1024 * 8,
}
fileSizesMap := map[string]int64{
"512KB": 1024 * 512,
"1MB": 1024 * 1024,
"10MB": 1024 * 1024 * 10,
"100MB": 1024 * 1024 * 100,
"1GB": 1024 * 1024 * 1000,
}
// To force order
bufSizeLabels := []string{"32KB", "128KB", "1MB", "2MB", "4MB", "8MB"}
fileSizeLabels := []string{"512KB", "1MB", "10MB", "100MB", "1GB"}
b.Run("plain", func(b *testing.B) {
b.Run("local", func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
b.StopTimer()
_, err := fileA.Seek(0, io.SeekStart)
require.NoError(b, err)
_, err = fileB.Seek(0, io.SeekStart)
require.NoError(b, err)
b.StartTimer()
ok, err := compareFilesContent(fileA, fileB, 0)
require.NoError(b, err)
require.True(b, ok)
}
})
b.Run("s3", func(b *testing.B) {
b.ReportAllocs()
th := SetupConfig(b, func(cfg *model.Config) {
cfg.FileSettings = model.FileSettings{
DriverName: model.NewPointer(model.ImageDriverS3),
AmazonS3AccessKeyId: model.NewPointer(model.MinioAccessKey),
AmazonS3SecretAccessKey: model.NewPointer(model.MinioSecretKey),
AmazonS3Bucket: model.NewPointer("comparefilescontentbucket"),
AmazonS3Endpoint: model.NewPointer("localhost:9000"),
AmazonS3Region: model.NewPointer(""),
AmazonS3PathPrefix: model.NewPointer(""),
AmazonS3SSL: model.NewPointer(false),
AmazonS3RequestTimeoutMilliseconds: model.NewPointer(int64(300 * 1000)),
}
})
err := th.App.Srv().FileBackend().(*filestore.S3FileBackend).TestConnection()
require.NoError(b, err)
_, err = fileA.Seek(0, io.SeekStart)
require.NoError(b, err)
_, err = fileB.Seek(0, io.SeekStart)
require.NoError(b, err)
_, appErr := th.App.WriteFile(fileA, "compareFileA")
require.Nil(b, appErr)
defer func() {
err = th.App.RemoveFile("compareFileA")
require.NoError(b, err)
}()
_, appErr = th.App.WriteFile(fileB, "compareFileB")
require.Nil(b, appErr)
defer func() {
appErr = th.App.RemoveFile("compareFileB")
require.Nil(b, appErr)
}()
rdA, appErr := th.App.FileReader("compareFileA")
require.Nil(b, appErr)
defer func() {
err = rdA.Close()
require.NoError(b, err)
}()
rdB, appErr := th.App.FileReader("compareFileB")
require.Nil(b, appErr)
defer func() {
err = rdB.Close()
require.NoError(b, err)
}()
for _, fileSizeLabel := range fileSizeLabels {
fileSize := fileSizesMap[fileSizeLabel]
for _, bufSizeLabel := range bufSizeLabels {
bufSize := bufSizesMap[bufSizeLabel]
b.Run("bufSize-fileSize"+fileSizeLabel+"-bufSize"+bufSizeLabel, func(b *testing.B) {
for b.Loop() {
b.StopTimer()
_, err := rdA.Seek(0, io.SeekStart)
require.NoError(b, err)
_, err = rdB.Seek(0, io.SeekStart)
require.NoError(b, err)
b.StartTimer()
ok, err := compareFilesContent(&io.LimitedReader{
R: rdA,
N: fileSize,
}, &io.LimitedReader{
R: rdB,
N: fileSize,
}, bufSize)
require.NoError(b, err)
require.True(b, ok)
}
})
}
}
})
})
b.Run("zip", func(b *testing.B) {
b.ReportAllocs()
zipFilePath := filepath.Join(tmpDir, "compareFiles.zip")
zipFile, err := os.Create(zipFilePath)
require.NoError(b, err)
defer func() {
err = zipFile.Close()
require.NoError(b, err)
err = os.Remove(zipFilePath)
require.NoError(b, err)
}()
zipWr := zip.NewWriter(zipFile)
fileAZipWr, err := zipWr.CreateHeader(&zip.FileHeader{
Name: "compareFileA",
Method: zip.Store,
})
require.NoError(b, err)
_, err = io.Copy(fileAZipWr, fileA)
require.NoError(b, err)
fileBZipWr, err := zipWr.CreateHeader(&zip.FileHeader{
Name: "compareFileB",
Method: zip.Store,
})
require.NoError(b, err)
_, err = io.Copy(fileBZipWr, fileB)
require.NoError(b, err)
err = zipWr.Close()
require.NoError(b, err)
info, err := zipFile.Stat()
require.NoError(b, err)
zipFileSize := info.Size()
b.Run("local", func(b *testing.B) {
for _, label := range bufSizeLabels {
bufSize := bufSizesMap[label]
b.Run("bufSize-"+label, func(b *testing.B) {
for b.Loop() {
b.StopTimer()
_, err := zipFile.Seek(0, io.SeekStart)
require.NoError(b, err)
zipRd, err := zip.NewReader(zipFile, zipFileSize)
require.NoError(b, err)
zipFileA, err := zipRd.Open("compareFileA")
require.NoError(b, err)
zipFileB, err := zipRd.Open("compareFileB")
require.NoError(b, err)
b.StartTimer()
ok, err := compareFilesContent(zipFileA, zipFileB, bufSize)
require.NoError(b, err)
require.True(b, ok)
}
})
}
})
b.Run("s3", func(b *testing.B) {
th := SetupConfig(b, func(cfg *model.Config) {
cfg.FileSettings = model.FileSettings{
DriverName: model.NewPointer(model.ImageDriverS3),
AmazonS3AccessKeyId: model.NewPointer(model.MinioAccessKey),
AmazonS3SecretAccessKey: model.NewPointer(model.MinioSecretKey),
AmazonS3Bucket: model.NewPointer("comparefilescontentbucket"),
AmazonS3Endpoint: model.NewPointer("localhost:9000"),
AmazonS3Region: model.NewPointer(""),
AmazonS3PathPrefix: model.NewPointer(""),
AmazonS3SSL: model.NewPointer(false),
AmazonS3RequestTimeoutMilliseconds: model.NewPointer(int64(300 * 1000)),
}
})
err := th.App.Srv().FileBackend().(*filestore.S3FileBackend).TestConnection()
require.NoError(b, err)
_, appErr := th.App.WriteFile(zipFile, "compareFiles.zip")
require.Nil(b, appErr)
defer func() {
appErr = th.App.RemoveFile("compareFiles.zip")
require.Nil(b, appErr)
}()
zipFileRd, appErr := th.App.FileReader("compareFiles.zip")
require.Nil(b, appErr)
defer func() {
err = zipFileRd.Close()
require.NoError(b, err)
}()
for _, fileSizeLabel := range fileSizeLabels {
fileSize := fileSizesMap[fileSizeLabel]
for _, bufSizeLabel := range bufSizeLabels {
bufSize := bufSizesMap[bufSizeLabel]
b.Run("bufSize-fileSize"+fileSizeLabel+"-bufSize"+bufSizeLabel, func(b *testing.B) {
for b.Loop() {
b.StopTimer()
_, err := zipFileRd.Seek(0, io.SeekStart)
require.NoError(b, err)
zipRd, err := zip.NewReader(zipFileRd.(io.ReaderAt), zipFileSize)
require.NoError(b, err)
zipFileA, err := zipRd.Open("compareFileA")
require.NoError(b, err)
zipFileB, err := zipRd.Open("compareFileB")
require.NoError(b, err)
b.StartTimer()
ok, err := compareFilesContent(&io.LimitedReader{
R: zipFileA,
N: fileSize,
}, &io.LimitedReader{
R: zipFileB,
N: fileSize,
}, bufSize)
require.NoError(b, err)
require.True(b, ok)
}
})
}
}
})
})
}