Files
zeekay 8feb24ea1f One name for this fork, and it is ours
The server module named github.com/hanzoai/team/server/v8 — a path that
resolves to the retired workspace's repository, not this one — while
server/public still published github.com/mattermost/mattermost/server/public.
Both are github.com/hanzoteam/server/... now, which is where this code
actually lives.

That second half is what made the binary unbootable. Upstream's public path
stayed claimed by upstream, mattermost-plugin-ai imported it, and the build
linked public/model twice; public/plugin's gob.Register then ran twice under
one name and panicked at init, before main. Reverting the rename traded the
brand for a working binary. Rebranding the plugin alongside it needs no such
trade: the agents fork is github.com/hanzoteam/agents/v2 against our public
module, so no module named mattermost/mattermost is in the graph at all.

Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and
it is ours.
2026-08-28 23:35:36 -07:00

332 lines
14 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"database/sql"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/hanzoteam/server/server/public/model"
"github.com/hanzoteam/server/server/public/shared/mlog"
"github.com/hanzoteam/server/server/v8/channels/db"
)
func readMigrationSQL(t *testing.T, filename string) string {
t.Helper()
data, err := db.Assets().ReadFile("migrations/postgres/" + filename)
require.NoError(t, err, "failed to read migration file %s", filename)
return string(data)
}
func TestMigration000185(t *testing.T) {
logger := mlog.CreateTestLogger(t)
settings, err := makeSqlSettings(model.DatabaseDriverPostgres)
if err != nil {
t.Skip(err)
}
store, err := New(*settings, logger, nil)
require.NoError(t, err)
defer store.Close()
master := store.GetMaster()
upSQL := readMigrationSQL(t, "000176_migrate_cpa_to_access_control.up.sql")
downSQL := readMigrationSQL(t, "000176_migrate_cpa_to_access_control.down.sql")
// Insert a group simulating pre-migration CPA state.
groupID := model.NewId()
_, err = master.Exec("INSERT INTO PropertyGroups (ID, Name) VALUES (?, ?)", groupID, "custom_profile_attributes")
require.NoError(t, err)
t.Cleanup(func() {
master.Exec("DELETE FROM PropertyValues WHERE GroupID = ?", groupID) //nolint:errcheck
master.Exec("DELETE FROM PropertyFields WHERE GroupID = ?", groupID) //nolint:errcheck
master.Exec("DELETE FROM PropertyGroups WHERE ID = ?", groupID) //nolint:errcheck
})
now := model.GetMillis()
// Insert active fields with old format (no ObjectType, no permissions).
// fieldID1 and fieldID2 are non-managed; fieldID3 is admin-managed.
fieldID1 := model.NewId()
fieldID2 := model.NewId()
fieldID3 := model.NewId()
for _, f := range []struct {
id string
name string
ftype string
attrs string
}{
{fieldID1, "Text Field", "text", `{"visibility":"always","sort_order":1}`},
{fieldID2, "Select Field", "select", `{"options":[{"id":"opt1","name":"Option 1"}]}`},
{fieldID3, "Admin Managed Field", "text", `{"visibility":"always","sort_order":3,"managed":"admin"}`},
} {
_, err = master.Exec(
`INSERT INTO PropertyFields
(ID, GroupID, Name, Type, Attrs, TargetID, TargetType, ObjectType, CreateAt, UpdateAt, DeleteAt, Protected)
VALUES (?, ?, ?, ?, ?::jsonb, '', '', '', ?, ?, 0, false)`,
f.id, groupID, f.name, f.ftype, f.attrs, now, now,
)
require.NoError(t, err, "inserting field %s", f.name)
}
// Insert a soft-deleted field to verify all fields are migrated.
deletedFieldID := model.NewId()
_, err = master.Exec(
`INSERT INTO PropertyFields
(ID, GroupID, Name, Type, Attrs, TargetID, TargetType, ObjectType, CreateAt, UpdateAt, DeleteAt, Protected)
VALUES (?, ?, 'Deleted Field', 'text', '{}'::jsonb, '', '', '', ?, ?, ?, false)`,
deletedFieldID, groupID, now, now, now,
)
require.NoError(t, err)
// Insert a property value.
valueID := model.NewId()
targetUserID := model.NewId()
_, err = master.Exec(
`INSERT INTO PropertyValues
(ID, TargetID, TargetType, GroupID, FieldID, Value, CreateAt, UpdateAt, DeleteAt)
VALUES (?, ?, 'user', ?, ?, '"hello"'::jsonb, ?, ?, 0)`,
valueID, targetUserID, groupID, fieldID1, now, now,
)
require.NoError(t, err)
// ---- Run UP migration ----
_, err = master.ExecNoTimeout(upSQL)
require.NoError(t, err, "up migration should succeed")
// Verify: group renamed.
var groupName string
require.NoError(t, master.Get(&groupName, "SELECT Name FROM PropertyGroups WHERE ID = ?", groupID))
assert.Equal(t, "access_control", groupName)
// Verify: all fields (including soft-deleted) have new metadata.
// Non-managed fields get PermissionValues = 'member'.
// Admin-managed fields get PermissionValues = 'sysadmin'.
for _, tc := range []struct {
id string
label string
expectedPermissionValues string
}{
{fieldID1, "non-managed text field", "member"},
{fieldID2, "non-managed select field", "member"},
{fieldID3, "admin-managed field", "sysadmin"},
{deletedFieldID, "soft-deleted non-managed field", "member"},
} {
var f struct {
ObjectType string `db:"objecttype"`
TargetType string `db:"targettype"`
PermissionField sql.NullString `db:"permissionfield"`
PermissionValues sql.NullString `db:"permissionvalues"`
PermissionOptions sql.NullString `db:"permissionoptions"`
}
require.NoError(t, master.Get(&f, "SELECT ObjectType, TargetType, PermissionField, PermissionValues, PermissionOptions FROM PropertyFields WHERE ID = ?", tc.id))
assert.Equal(t, "user", f.ObjectType, "%s ObjectType", tc.label)
assert.Equal(t, "system", f.TargetType, "%s TargetType", tc.label)
assert.True(t, f.PermissionField.Valid, "%s PermissionField should be set", tc.label)
assert.Equal(t, "sysadmin", f.PermissionField.String, "%s PermissionField", tc.label)
assert.True(t, f.PermissionValues.Valid, "%s PermissionValues should be set", tc.label)
assert.Equal(t, tc.expectedPermissionValues, f.PermissionValues.String, "%s PermissionValues", tc.label)
assert.True(t, f.PermissionOptions.Valid, "%s PermissionOptions should be set", tc.label)
assert.Equal(t, "sysadmin", f.PermissionOptions.String, "%s PermissionOptions", tc.label)
}
// Verify: property value is unchanged (GroupID still references the same ID).
var val struct {
GroupID string `db:"groupid"`
TargetID string `db:"targetid"`
TargetType string `db:"targettype"`
}
require.NoError(t, master.Get(&val, "SELECT GroupID, TargetID, TargetType FROM PropertyValues WHERE ID = ?", valueID))
assert.Equal(t, groupID, val.GroupID, "value GroupID should be unchanged")
assert.Equal(t, targetUserID, val.TargetID, "value TargetID should be unchanged")
assert.Equal(t, "user", val.TargetType, "value TargetType should be unchanged")
// Verify: AttributeView exists and includes the ObjectType filter (user-type fields only).
var viewDef string
err = master.Get(&viewDef, "SELECT definition FROM pg_matviews WHERE matviewname = 'attributeview'")
require.NoError(t, err, "AttributeView should exist")
assert.Contains(t, viewDef, "pf.objecttype", "view definition should filter by pf.ObjectType")
// Verify: materialized view contains expected data after refresh.
_, err = master.ExecNoTimeout("REFRESH MATERIALIZED VIEW AttributeView")
require.NoError(t, err, "refreshing AttributeView should succeed")
var viewRow struct {
GroupID string `db:"groupid"`
TargetID string `db:"targetid"`
TargetType string `db:"targettype"`
Attributes []byte `db:"attributes"`
}
err = master.Get(&viewRow, "SELECT GroupID, TargetID, TargetType, Attributes FROM AttributeView WHERE TargetID = ?", targetUserID)
require.NoError(t, err, "AttributeView should contain a row for the target user")
assert.Equal(t, groupID, viewRow.GroupID)
assert.Equal(t, targetUserID, viewRow.TargetID)
assert.Equal(t, "user", viewRow.TargetType)
// The text field value "hello" should appear under the field name "Text Field".
var attrs map[string]json.RawMessage
require.NoError(t, json.Unmarshal(viewRow.Attributes, &attrs))
assert.JSONEq(t, `"hello"`, string(attrs["Text Field"]), "text field value should be materialized")
// ---- Run DOWN migration ----
_, err = master.ExecNoTimeout(downSQL)
require.NoError(t, err, "down migration should succeed")
// Verify: group name reverted.
require.NoError(t, master.Get(&groupName, "SELECT Name FROM PropertyGroups WHERE ID = ?", groupID))
assert.Equal(t, "custom_profile_attributes", groupName)
// Verify: fields reverted.
for _, fid := range []string{fieldID1, fieldID2, fieldID3, deletedFieldID} {
var f struct {
ObjectType string `db:"objecttype"`
TargetType string `db:"targettype"`
PermissionField sql.NullString `db:"permissionfield"`
PermissionValues sql.NullString `db:"permissionvalues"`
PermissionOptions sql.NullString `db:"permissionoptions"`
}
require.NoError(t, master.Get(&f, "SELECT ObjectType, TargetType, PermissionField, PermissionValues, PermissionOptions FROM PropertyFields WHERE ID = ?", fid))
assert.Equal(t, "", f.ObjectType, "field %s ObjectType should revert", fid)
assert.Equal(t, "", f.TargetType, "field %s TargetType should revert", fid)
assert.False(t, f.PermissionField.Valid, "field %s PermissionField should be NULL", fid)
assert.False(t, f.PermissionValues.Valid, "field %s PermissionValues should be NULL", fid)
assert.False(t, f.PermissionOptions.Valid, "field %s PermissionOptions should be NULL", fid)
}
// Verify: value still unchanged after down migration.
require.NoError(t, master.Get(&val, "SELECT GroupID, TargetID, TargetType FROM PropertyValues WHERE ID = ?", valueID))
assert.Equal(t, groupID, val.GroupID, "value GroupID should remain unchanged after down")
}
func TestMigration000185DownPreservesNonUserFields(t *testing.T) {
logger := mlog.CreateTestLogger(t)
settings, err := makeSqlSettings(model.DatabaseDriverPostgres)
if err != nil {
t.Skip(err)
}
store, err := New(*settings, logger, nil)
require.NoError(t, err)
defer store.Close()
master := store.GetMaster()
upSQL := readMigrationSQL(t, "000176_migrate_cpa_to_access_control.up.sql")
downSQL := readMigrationSQL(t, "000176_migrate_cpa_to_access_control.down.sql")
groupID := model.NewId()
_, err = master.Exec("INSERT INTO PropertyGroups (ID, Name) VALUES (?, ?)", groupID, "custom_profile_attributes")
require.NoError(t, err)
t.Cleanup(func() {
master.Exec("DELETE FROM PropertyFields WHERE GroupID = ?", groupID) //nolint:errcheck
master.Exec("DELETE FROM PropertyGroups WHERE ID = ?", groupID) //nolint:errcheck
})
now := model.GetMillis()
// Insert a legacy user field that the up migration will touch.
userFieldID := model.NewId()
_, err = master.Exec(
`INSERT INTO PropertyFields
(ID, GroupID, Name, Type, Attrs, TargetID, TargetType, ObjectType, CreateAt, UpdateAt, DeleteAt, Protected)
VALUES (?, ?, 'Legacy User Field', 'text', '{}'::jsonb, '', '', '', ?, ?, 0, false)`,
userFieldID, groupID, now, now,
)
require.NoError(t, err)
// Run UP migration — legacy user field gets ObjectType='user', TargetType='system'.
_, err = master.ExecNoTimeout(upSQL)
require.NoError(t, err, "up migration should succeed")
// Simulate a post-migration channel-scoped field created via the
// generic property API against the (now renamed) access_control
// group.
channelFieldID := model.NewId()
channelTargetID := model.NewId()
_, err = master.Exec(
`INSERT INTO PropertyFields
(ID, GroupID, Name, Type, Attrs, TargetID, TargetType, ObjectType, PermissionField, PermissionValues, PermissionOptions, CreateAt, UpdateAt, DeleteAt, Protected)
VALUES (?, ?, 'Channel Classification', 'select', '{}'::jsonb, ?, 'channel', 'channel', 'sysadmin', 'member', 'sysadmin', ?, ?, 0, false)`,
channelFieldID, groupID, channelTargetID, now, now,
)
require.NoError(t, err)
// Run DOWN migration — must revert only user/system fields, not the channel one.
_, err = master.ExecNoTimeout(downSQL)
require.NoError(t, err, "down migration should succeed")
// The original user field reverts to legacy metadata.
var userField struct {
ObjectType string `db:"objecttype"`
TargetType string `db:"targettype"`
PermissionField sql.NullString `db:"permissionfield"`
PermissionValues sql.NullString `db:"permissionvalues"`
PermissionOptions sql.NullString `db:"permissionoptions"`
}
require.NoError(t, master.Get(&userField, "SELECT ObjectType, TargetType, PermissionField, PermissionValues, PermissionOptions FROM PropertyFields WHERE ID = ?", userFieldID))
assert.Equal(t, "", userField.ObjectType, "user field ObjectType should revert")
assert.Equal(t, "", userField.TargetType, "user field TargetType should revert")
assert.False(t, userField.PermissionField.Valid, "user field PermissionField should be NULL")
// The post-migration channel field keeps its PSAv2 metadata intact.
var channelField struct {
ObjectType string `db:"objecttype"`
TargetType string `db:"targettype"`
TargetID string `db:"targetid"`
PermissionField sql.NullString `db:"permissionfield"`
PermissionValues sql.NullString `db:"permissionvalues"`
PermissionOptions sql.NullString `db:"permissionoptions"`
}
require.NoError(t, master.Get(&channelField, "SELECT ObjectType, TargetType, TargetID, PermissionField, PermissionValues, PermissionOptions FROM PropertyFields WHERE ID = ?", channelFieldID))
assert.Equal(t, "channel", channelField.ObjectType, "channel field ObjectType must survive rollback")
assert.Equal(t, "channel", channelField.TargetType, "channel field TargetType must survive rollback")
assert.Equal(t, channelTargetID, channelField.TargetID, "channel field TargetID must survive rollback")
assert.True(t, channelField.PermissionField.Valid, "channel field PermissionField must survive rollback")
assert.Equal(t, "sysadmin", channelField.PermissionField.String)
assert.True(t, channelField.PermissionValues.Valid)
assert.Equal(t, "member", channelField.PermissionValues.String)
assert.True(t, channelField.PermissionOptions.Valid)
assert.Equal(t, "sysadmin", channelField.PermissionOptions.String)
}
func TestMigration000185NoOpOnFreshDB(t *testing.T) {
logger := mlog.CreateTestLogger(t)
settings, err := makeSqlSettings(model.DatabaseDriverPostgres)
if err != nil {
t.Skip(err)
}
store, err := New(*settings, logger, nil)
require.NoError(t, err)
defer store.Close()
master := store.GetMaster()
upSQL := readMigrationSQL(t, "000176_migrate_cpa_to_access_control.up.sql")
downSQL := readMigrationSQL(t, "000176_migrate_cpa_to_access_control.down.sql")
// On a fresh database with no CPA group, both up and down should be
// safe no-ops (the UPDATE statements match zero rows).
_, err = master.ExecNoTimeout(upSQL)
assert.NoError(t, err, "up migration should be a safe no-op on fresh DB")
// Even with no CPA data, the view should be (re)created.
var viewExists bool
require.NoError(t, master.Get(&viewExists, "SELECT EXISTS (SELECT 1 FROM pg_matviews WHERE matviewname = 'attributeview')"))
assert.True(t, viewExists, "AttributeView should exist after up migration on fresh DB")
_, err = master.ExecNoTimeout(downSQL)
assert.NoError(t, err, "down migration should be a safe no-op on fresh DB")
}