Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/office/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
OOM-immune processes (`oom_score_adj <= -900`) and hides on hosts
without `/proc`.

- Hub test coverage for the ecosystem proxy (DL-17a7): `test_hub.py`
gains an integration case that stands up a real upstream HTTP server
serving an eco payload, points `OFFICE_ECO_URL` at it, and asserts
`GET /api/eco` returns that payload under the `"eco"` key next to the
office state — locking in the chain the `#eco` side panel depends on
(office daemon `/api/eco-raw` → rtunnel → `OFFICE_ECO_URL` → hub
`/api/eco`). A second case asserts the unreachable-upstream fallback
(`{"error": "ecosystem API unreachable"}`).

### Fixed

- `session_locked()` on Linux never detected open sessions: POSIX locks
Expand Down
74 changes: 73 additions & 1 deletion packages/office/tests/test_hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,31 @@
import unittest
import urllib.error
import urllib.request
from http.server import ThreadingHTTPServer
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from unittest.mock import patch

from devin_office import hub


def _eco_upstream(payload: dict) -> ThreadingHTTPServer:
"""HTTP server that serves a fixed eco payload — stands in for the
office daemon's /api/eco-raw behind OFFICE_ECO_URL."""
body = json.dumps(payload).encode()

class _Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)

def log_message(self, *args):
pass

return ThreadingHTTPServer(("127.0.0.1", 0), _Handler)


class HubSecurityTests(unittest.TestCase):
def test_loopback_can_run_without_a_token(self):
hub.validate_binding("127.0.0.1", "")
Expand Down Expand Up @@ -55,6 +74,59 @@ def test_ecosystem_proxy_is_disabled_by_default(self):
self.assertIsNone(result["eco"])
self.assertEqual(result["office"], {"agents": []})

def test_ecosystem_proxy_returns_upstream_data(self):
Comment thread
Icaro0310 marked this conversation as resolved.
Resolved
"""DL-17a7: with OFFICE_ECO_URL set, GET /api/eco must proxy the
upstream eco payload so the side panel renders real data."""
upstream_payload = {
"office-laptop": {
"probes": {"mcp-hub": 200, "vm-office": 200},
"tools": {"unified-mcp": True, "slack-poll": False},
"units": {"devin-janitor.timer": "active"},
"meta": {"repos": 10, "clis": 42},
}
}
upstream = _eco_upstream(upstream_payload)
upstream_thread = threading.Thread(
target=upstream.serve_forever, daemon=True)
upstream_thread.start()
server = ThreadingHTTPServer(("127.0.0.1", 0), hub.Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
eco_url = f"http://127.0.0.1:{upstream.server_port}/api/eco-raw"
try:
with patch.object(hub, "ECO_URL", eco_url), patch.object(
hub, "TOKEN", ""
), patch.object(
hub, "_eco_cache", {"at": 0.0, "data": None}
), patch.object(
hub, "current_state", return_value={"agents": []}
):
thread.start()
base = f"http://127.0.0.1:{server.server_port}"
with urllib.request.urlopen(base + "/api/eco") as resp:
data = json.loads(resp.read().decode())
self.assertEqual(data["eco"], upstream_payload)
self.assertEqual(data["office"], {"agents": []})
finally:
server.shutdown()
server.server_close()
thread.join()
upstream.shutdown()
upstream.server_close()
upstream_thread.join()

def test_ecosystem_proxy_reports_unreachable_upstream(self):
with patch.object(hub, "ECO_URL", "http://127.0.0.1:9/eco"), patch.object(
hub, "_eco_cache", {"at": 0.0, "data": None}
), patch.object(
hub, "current_state", return_value={"agents": []}
), patch(
"urllib.request.urlopen", side_effect=OSError("refused")
):
result = hub.eco_status()
self.assertEqual(result["eco"],
{"error": "ecosystem API unreachable"})
self.assertEqual(result["office"], {"agents": []})


if __name__ == "__main__":
unittest.main()
Loading