Skip to content

Commit 714f207

Browse files
committed
improvements
1 parent 7b1a7fc commit 714f207

File tree

5 files changed

+140
-7
lines changed

5 files changed

+140
-7
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import pytest
2+
import importlib
3+
4+
from starlette.testclient import TestClient
5+
6+
@pytest.fixture
7+
def clear_env(monkeypatch):
8+
"""
9+
A helper fixture to remove/clear environment variables before each test
10+
so each test starts from a known baseline.
11+
"""
12+
monkeypatch.delenv("HASSIO_MODE", raising=False)
13+
monkeypatch.delenv("HASSIO_INGRESS_PATH", raising=False)
14+
15+
16+
def _reload_app_and_config():
17+
"""
18+
Helper that re-imports get_config and reloads app.fastapi.
19+
20+
This helps ensure that changing environment variables
21+
re-applies to the loaded config and routes.
22+
"""
23+
# Force a re-import of get_config
24+
from app import config
25+
importlib.reload(config)
26+
# Force a reload of the entire FastAPI module
27+
from app import fastapi
28+
importlib.reload(fastapi)
29+
# Return the reloaded references
30+
return fastapi.app, config.get_config()
31+
32+
33+
@pytest.mark.asyncio
34+
async def test_no_hassio_env(clear_env):
35+
"""
36+
With no HASSIO_MODE and no HASSIO_INGRESS_PATH set,
37+
we expect HASSIO_MODE=None, HASSIO_INGRESS_PATH=None, and
38+
routes are just "/api" for the API + root HTML at "/".
39+
"""
40+
app, conf = _reload_app_and_config()
41+
42+
assert conf.HASSIO_MODE is None
43+
assert conf.HASSIO_INGRESS_PATH is None
44+
assert conf.base_path == ""
45+
46+
client = TestClient(app)
47+
48+
# /api route should work normally
49+
resp_api = client.get("/api/frames")
50+
assert resp_api.status_code != 404, "API route at /api should be accessible."
51+
52+
# Root HTML
53+
resp_root = client.get("/")
54+
assert resp_root.status_code == 200, "Root HTML / should be served normally."
55+
56+
57+
@pytest.mark.asyncio
58+
async def test_hassio_mode_public(clear_env, monkeypatch):
59+
"""
60+
HASSIO_MODE=public means we only mount /api/log as a 'public' router
61+
and do *not* serve the normal HTML routes.
62+
"""
63+
monkeypatch.setenv("HASSIO_MODE", "public")
64+
65+
app, conf = _reload_app_and_config()
66+
67+
assert conf.HASSIO_MODE == "public"
68+
assert conf.HASSIO_INGRESS_PATH is None
69+
assert conf.base_path == ""
70+
71+
client = TestClient(app)
72+
73+
# The /api route is still there
74+
resp_api = client.get("/api/frames")
75+
assert resp_api.status_code == 404, "/api route should not be accessible in public mode."
76+
77+
resp_api = client.post("/api/log")
78+
assert resp_api.status_code != 404, "/api/log route should still be accessible in public mode."
79+
80+
# But the root HTML route ("/") won't be mounted.
81+
# We expect e.g. a 404 or similar because serve_html = False in "public" mode.
82+
resp_root = client.get("/")
83+
assert resp_root.status_code == 404, (
84+
"In public (ingress-public) mode, the HTML root (/) is typically not served."
85+
)
86+
87+
88+
@pytest.mark.asyncio
89+
async def test_hassio_mode_ingress(clear_env, monkeypatch):
90+
"""
91+
HASSIO_MODE=ingress with a HASSIO_INGRESS_PATH means the 'base_path'
92+
is set to that path. Our APIs and HTML routes are prefixed with that base.
93+
"""
94+
custom_ingress = "/my_ingress_path"
95+
monkeypatch.setenv("HASSIO_MODE", "ingress")
96+
monkeypatch.setenv("HASSIO_INGRESS_PATH", custom_ingress)
97+
98+
app, conf = _reload_app_and_config()
99+
100+
assert conf.HASSIO_MODE == "ingress"
101+
assert conf.HASSIO_INGRESS_PATH == custom_ingress
102+
assert conf.base_path == custom_ingress
103+
104+
client = TestClient(app)
105+
106+
# Check that /my_ingress_path/api is the new route
107+
resp_api = client.get(f"{custom_ingress}/api/frames")
108+
assert resp_api.status_code != 404, (
109+
f"Expected to find the /api routes under base_path={custom_ingress}."
110+
)
111+
112+
# Root HTML is also served at /my_ingress_path
113+
resp_root = client.get(custom_ingress)
114+
assert resp_root.status_code == 200, "HTML root should be served at /{ingress_path} in ingress mode."
115+
116+
# Check that /api (without the ingress path) returns 404
117+
# because in ingress mode, everything is behind the prefix
118+
resp_api_no_prefix = client.get("/api/frames")
119+
assert resp_api_no_prefix.status_code == 404, (
120+
"In ingress mode, /api/* should not exist without the base path prefix."
121+
)

backend/app/fastapi.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,15 @@ async def lifespan(app: FastAPI):
5555

5656
# Public config for the frontend
5757
frameos_app_config = {}
58-
index_html = open("../frontend/dist/index.html").read()
58+
try:
59+
index_html = open("../frontend/dist/index.html").read()
60+
except FileNotFoundError:
61+
if config.TEST:
62+
# don't need the compiled frontend when testing
63+
index_html = open("../frontend/src/index.html").read()
64+
else:
65+
raise
66+
5967
if config.HASSIO_MODE:
6068
frameos_app_config["HASSIO_MODE"] = config.HASSIO_MODE
6169
if config.base_path:

frontend/src/scenes/scenes.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ export const getRoutes = () =>
1515
[urls.frames()]: 'frames',
1616
[urls.frame(':id')]: 'frame',
1717
[urls.settings()]: 'settings',
18-
'/login': 'login',
19-
'/signup': 'signup',
18+
[urls.login()]: 'login',
19+
[urls.signup()]: 'signup',
2020
} as const)

frontend/src/urls.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ export const urls = {
44
frames: () => (getBasePath() ? getBasePath() : '/'),
55
frame: (id: number | string) => getBasePath() + '/frames/' + id,
66
settings: () => getBasePath() + '/settings',
7+
login: () => getBasePath() + '/login',
8+
logout: () => getBasePath() + '/logout',
9+
signup: () => getBasePath() + '/signup',
710
} as const

frontend/src/utils/apiFetch.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { router } from 'kea-router'
22
import { inHassioIngress } from './inHassioIngress'
33
import { getBasePath } from './getBasePath'
4+
import { urls } from '../urls'
45

56
export interface ApiFetchOptions extends RequestInit {}
67

@@ -30,10 +31,10 @@ export async function apiFetch(input: RequestInfo | URL, options: ApiFetchOption
3031
} else if (!inHassioIngress()) {
3132
const exists = await userExists()
3233
if (exists) {
33-
router.actions.push('/login')
34+
router.actions.push(urls.login())
3435
return new Promise(() => {})
3536
} else {
36-
router.actions.push('/signup')
37+
router.actions.push(urls.signup())
3738
return new Promise(() => {})
3839
}
3940
}
@@ -47,9 +48,9 @@ export async function apiFetch(input: RequestInfo | URL, options: ApiFetchOption
4748
if (!inHassioIngress() && response.status === 401) {
4849
const exists = await userExists()
4950
if (exists) {
50-
router.actions.push('/login')
51+
router.actions.push(urls.login())
5152
} else {
52-
router.actions.push('/signup')
53+
router.actions.push(urls.signup())
5354
}
5455
return new Promise(() => {})
5556
}

0 commit comments

Comments
 (0)