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) } }) }