feat(matrix): MAS migration helpers + 2 flows + 15 issues + capability group
Helper functions (matrix-mas capability group): - mas_client_register_bash_infra: register/sync OAuth clients via mas-cli - mas_syn2mas_migration_bash_infra: dry-run + apply user migration to MAS - synapse_msc3861_enable_go_infra: edit homeserver.yaml MSC3861 block (with diff) - wellknown_oidc_patch_go_infra: patch well-known JSON with msc2965.authentication - synapse_login_flows_check_go_infra: health-check post-migration login flows Flows + issues for custom Matrix clients (PC + Android): - 0010 matrix-client-pc: Wails + React+Mantine (issues 0147-0153) - 0011 matrix-client-android: Kotlin + Compose (issues 0154-0161) - 0162 enable MAS as auth provider (Synapse delegate) — EXECUTED on VPS - 0163 custom admin panel propio (sustituye synapse-admin) Production state (organic-machine.com): - Synapse migrated SQLite -> Postgres - MSC3861 active, password_config disabled - 21 users + 41 access_tokens migrated via syn2mas - 4 MAS clients registered (element, matrix_pc, matrix_android, admin_panel) - synapse-admin container removed + Coolify route deleted - well-known patched with org.matrix.msc2965.authentication Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// fixtureWellknown is the real-world JSON from the VPS wellknown container,
|
||||
// with m.homeserver and org.matrix.msc4143.rtc_foci already present.
|
||||
const fixtureWellknown = `{
|
||||
"m.homeserver": {
|
||||
"base_url": "https://matrix.organic-machine.com"
|
||||
},
|
||||
"org.matrix.msc4143.rtc_foci": [
|
||||
{
|
||||
"type": "livekit",
|
||||
"livekit_service_url": "https://livekit.organic-machine.com"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
func TestWellknownOidcPatch(t *testing.T) {
|
||||
const issuer = "https://auth-af2f3d.organic-machine.com/"
|
||||
const accountURL = "https://auth-af2f3d.organic-machine.com/account"
|
||||
|
||||
t.Run("patch adds key and preserves existing fields", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
jsonPath := filepath.Join(dir, "client")
|
||||
backupDir := filepath.Join(dir, "backups")
|
||||
|
||||
if err := os.WriteFile(jsonPath, []byte(fixtureWellknown), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := WellknownOidcPatch(WellknownOidcPatchConfig{
|
||||
WellknownJsonPath: jsonPath,
|
||||
Issuer: issuer,
|
||||
AccountURL: accountURL,
|
||||
BackupDir: backupDir,
|
||||
DryRun: false,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !res.Modified {
|
||||
t.Error("want Modified=true, got false")
|
||||
}
|
||||
if res.BackupPath == "" {
|
||||
t.Error("want non-empty BackupPath")
|
||||
}
|
||||
|
||||
// Backup must exist.
|
||||
if _, err := os.Stat(res.BackupPath); err != nil {
|
||||
t.Errorf("backup file missing: %v", err)
|
||||
}
|
||||
|
||||
// Read written file and validate.
|
||||
written, err := os.ReadFile(jsonPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var doc map[string]any
|
||||
if err := json.Unmarshal(written, &doc); err != nil {
|
||||
t.Fatalf("written file is not valid JSON: %v", err)
|
||||
}
|
||||
|
||||
// New key must exist with correct values.
|
||||
auth, ok := doc["org.matrix.msc2965.authentication"]
|
||||
if !ok {
|
||||
t.Fatal("org.matrix.msc2965.authentication key missing")
|
||||
}
|
||||
authMap, ok := auth.(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("org.matrix.msc2965.authentication is not an object")
|
||||
}
|
||||
if authMap["issuer"] != issuer {
|
||||
t.Errorf("issuer: want %q, got %q", issuer, authMap["issuer"])
|
||||
}
|
||||
if authMap["account"] != accountURL {
|
||||
t.Errorf("account: want %q, got %q", accountURL, authMap["account"])
|
||||
}
|
||||
|
||||
// Existing keys must be preserved.
|
||||
if _, ok := doc["m.homeserver"]; !ok {
|
||||
t.Error("m.homeserver was removed — must be preserved")
|
||||
}
|
||||
if _, ok := doc["org.matrix.msc4143.rtc_foci"]; !ok {
|
||||
t.Error("org.matrix.msc4143.rtc_foci was removed — must be preserved")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("idempotent: second call returns Modified=false", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
jsonPath := filepath.Join(dir, "client")
|
||||
backupDir := filepath.Join(dir, "backups")
|
||||
|
||||
if err := os.WriteFile(jsonPath, []byte(fixtureWellknown), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cfg := WellknownOidcPatchConfig{
|
||||
WellknownJsonPath: jsonPath,
|
||||
Issuer: issuer,
|
||||
AccountURL: accountURL,
|
||||
BackupDir: backupDir,
|
||||
DryRun: false,
|
||||
}
|
||||
|
||||
if _, err := WellknownOidcPatch(cfg); err != nil {
|
||||
t.Fatalf("first call error: %v", err)
|
||||
}
|
||||
|
||||
res2, err := WellknownOidcPatch(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("second call error: %v", err)
|
||||
}
|
||||
if res2.Modified {
|
||||
t.Error("want Modified=false on second call, got true")
|
||||
}
|
||||
if res2.BackupPath != "" {
|
||||
t.Errorf("want empty BackupPath on no-op, got %q", res2.BackupPath)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("dry run does not write file", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
jsonPath := filepath.Join(dir, "client")
|
||||
backupDir := filepath.Join(dir, "backups")
|
||||
|
||||
if err := os.WriteFile(jsonPath, []byte(fixtureWellknown), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := WellknownOidcPatch(WellknownOidcPatchConfig{
|
||||
WellknownJsonPath: jsonPath,
|
||||
Issuer: issuer,
|
||||
AccountURL: accountURL,
|
||||
BackupDir: backupDir,
|
||||
DryRun: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !res.Modified {
|
||||
t.Error("want Modified=true on dry run with new key")
|
||||
}
|
||||
if res.BackupPath != "" {
|
||||
t.Errorf("want empty BackupPath on dry run, got %q", res.BackupPath)
|
||||
}
|
||||
|
||||
// Original file must be untouched.
|
||||
content, _ := os.ReadFile(jsonPath)
|
||||
if string(content) != fixtureWellknown {
|
||||
t.Error("file was modified during dry run")
|
||||
}
|
||||
|
||||
// BackupDir must not have been created.
|
||||
if _, err := os.Stat(backupDir); !os.IsNotExist(err) {
|
||||
t.Error("backup dir was created during dry run")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nonexistent file returns error", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
_, err := WellknownOidcPatch(WellknownOidcPatchConfig{
|
||||
WellknownJsonPath: filepath.Join(dir, "does_not_exist"),
|
||||
Issuer: issuer,
|
||||
AccountURL: accountURL,
|
||||
BackupDir: filepath.Join(dir, "backups"),
|
||||
DryRun: false,
|
||||
})
|
||||
if err == nil {
|
||||
t.Error("want error for nonexistent file, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user