Files
fn_registry/functions/infra/matrix_client_init_test.go
T
egutierrez c441366f89 feat(matrix-mas): 3 helpers for matrix_client_pc (issue 0147)
- mas_oidc_loopback_go_infra: OAuth2 PKCE + loopback HTTP for desktop login
- keyring_token_store_go_infra: persist OAuth tokens in SO keychain
- matrix_client_init_go_infra: init mautrix.Client from access_token + whoami

Plus go.work workspace including matrix_client_pc sub-repo for shared
import path during dev. All 3 fns tagged matrix-mas capability group.

Tests: TestMasOidcLoopback (15 cases), TestKeyringTokenStore (5 cases,
SKIP on headless), TestMatrixClientInit (6 cases) — all green/skip.

Refs: dev/issues/0147-matrix-client-pc-scaffold.md
Refs: dataforge/matrix_client_pc commit f28c2b1
2026-05-24 23:23:49 +02:00

196 lines
5.7 KiB
Go

package infra
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
// whoamiHandler devuelve un handler httptest que simula /_matrix/client/v3/account/whoami.
// Si statusCode != 200, devuelve un RespError de mautrix.
func whoamiHandler(t *testing.T, statusCode int, userID, deviceID string) http.HandlerFunc {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) {
if statusCode != http.StatusOK {
// mautrix espera JSON con errcode/error para errores Matrix
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
_ = json.NewEncoder(w).Encode(map[string]string{
"errcode": "M_UNKNOWN_TOKEN",
"error": "Invalid macaroon passed.",
})
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(map[string]string{
"user_id": userID,
"device_id": deviceID,
})
}
}
func TestMatrixClientInit(t *testing.T) {
t.Run("HomeserverURL invalido", func(t *testing.T) {
tmpDir := t.TempDir()
cfg := MatrixClientInitConfig{
HomeserverURL: "not-a-url",
UserID: "@user:server",
AccessToken: "mxat_test",
StoreDir: tmpDir,
}
_, err := MatrixClientInit(cfg)
if err == nil {
t.Fatal("esperaba error con HomeserverURL invalido, got nil")
}
if !strings.Contains(err.Error(), "HomeserverURL") {
t.Errorf("error deberia mencionar HomeserverURL, got: %v", err)
}
})
t.Run("UserID format invalido", func(t *testing.T) {
tmpDir := t.TempDir()
cfg := MatrixClientInitConfig{
HomeserverURL: "https://matrix.example.com",
UserID: "egutierrez",
AccessToken: "mxat_test",
StoreDir: tmpDir,
}
_, err := MatrixClientInit(cfg)
if err == nil {
t.Fatal("esperaba error con UserID invalido, got nil")
}
if !strings.Contains(err.Error(), "UserID") {
t.Errorf("error deberia mencionar UserID, got: %v", err)
}
})
t.Run("DeviceID vacio Whoami exitoso", func(t *testing.T) {
const testUserID = "@egutierrez:test.matrix.org"
const testDeviceID = "ABCDEF1234"
srv := httptest.NewServer(whoamiHandler(t, http.StatusOK, testUserID, testDeviceID))
defer srv.Close()
tmpDir := t.TempDir()
cfg := MatrixClientInitConfig{
HomeserverURL: srv.URL,
UserID: testUserID,
AccessToken: "mxat_valid_token",
DeviceID: "", // fuerza Whoami
StoreDir: tmpDir,
}
res, err := MatrixClientInit(cfg)
if err != nil {
t.Fatalf("esperaba nil error, got: %v", err)
}
if res.Client == nil {
t.Fatal("Client es nil")
}
if string(res.Client.DeviceID) != testDeviceID {
t.Errorf("DeviceID: got %q, want %q", res.Client.DeviceID, testDeviceID)
}
if string(res.Client.UserID) != testUserID {
t.Errorf("UserID: got %q, want %q", res.Client.UserID, testUserID)
}
if res.Client.AccessToken != "mxat_valid_token" {
t.Errorf("AccessToken: got %q, want %q", res.Client.AccessToken, "mxat_valid_token")
}
if res.StorePath == "" {
t.Error("StorePath no puede estar vacio")
}
})
t.Run("Whoami 401 token invalido", func(t *testing.T) {
srv := httptest.NewServer(whoamiHandler(t, http.StatusUnauthorized, "", ""))
defer srv.Close()
tmpDir := t.TempDir()
cfg := MatrixClientInitConfig{
HomeserverURL: srv.URL,
UserID: "@egutierrez:test.matrix.org",
AccessToken: "mxat_expired",
DeviceID: "", // fuerza Whoami
StoreDir: tmpDir,
}
_, err := MatrixClientInit(cfg)
if err == nil {
t.Fatal("esperaba error con token invalido, got nil")
}
// Debe mencionar token invalido o M_UNKNOWN_TOKEN
errStr := err.Error()
if !strings.Contains(errStr, "UNKNOWN_TOKEN") && !strings.Contains(errStr, "token") && !strings.Contains(errStr, "Whoami") {
t.Errorf("error deberia mencionar token/Whoami, got: %v", err)
}
})
t.Run("EnableCrypto true devuelve error not implemented", func(t *testing.T) {
tmpDir := t.TempDir()
cfg := MatrixClientInitConfig{
HomeserverURL: "https://matrix.example.com",
UserID: "@user:matrix.example.com",
AccessToken: "mxat_test",
StoreDir: tmpDir,
EnableCrypto: true,
}
_, err := MatrixClientInit(cfg)
if err == nil {
t.Fatal("esperaba error con EnableCrypto=true, got nil")
}
if !strings.Contains(err.Error(), "not implemented") {
t.Errorf("error deberia mencionar 'not implemented', got: %v", err)
}
if !strings.Contains(err.Error(), "0150") {
t.Errorf("error deberia mencionar issue 0150, got: %v", err)
}
})
t.Run("StoreDir se crea con permisos 0700", func(t *testing.T) {
if os.Getenv("GOOS") == "windows" {
t.Skip("permisos Unix no aplican en Windows")
}
const testUserID = "@egutierrez:test.matrix.org"
const testDeviceID = "TESTDEVICE01"
srv := httptest.NewServer(whoamiHandler(t, http.StatusOK, testUserID, testDeviceID))
defer srv.Close()
base := t.TempDir()
// StoreDir que no existe aun — debe crearse
storeDir := filepath.Join(base, "matrix_state", "egutierrez")
cfg := MatrixClientInitConfig{
HomeserverURL: srv.URL,
UserID: testUserID,
AccessToken: "mxat_valid",
DeviceID: testDeviceID,
StoreDir: storeDir,
}
res, err := MatrixClientInit(cfg)
if err != nil {
t.Fatalf("esperaba nil error, got: %v", err)
}
if res.StorePath != storeDir {
t.Errorf("StorePath: got %q, want %q", res.StorePath, storeDir)
}
info, err := os.Stat(storeDir)
if err != nil {
t.Fatalf("StoreDir no fue creado: %v", err)
}
if !info.IsDir() {
t.Error("StoreDir no es un directorio")
}
// Verificar permisos 0700 (solo propietario)
mode := info.Mode().Perm()
if mode != 0700 {
t.Errorf("permisos StoreDir: got %04o, want 0700", mode)
}
})
}