Phase 1: reproduzierbare HA-Testumgebung als Skript
Die urspruengliche Wegwerf-Testinstanz war verloren, weil sie nur als Anleitung existierte. testumgebung/aufsetzen.sh baut sie jetzt vollstaendig neu auf: Container, Nutzer, langlebiges Token, pyscript als Custom Component, Projektdateien und nachgebildete Fahrzeugsensoren als Template-Entitaeten, die ueber Helfer zur Laufzeit steuerbar sind. Verifiziert: pyscript veroeffentlicht alle acht Entitaeten, Fahrzeugsensoren liefern Werte, beide Smoke-Tests gruen.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
// Legt in einer frischen Home-Assistant-Instanz einen Testnutzer an und gibt ein
|
||||
// langlebiges Zugriffstoken auf der Standardausgabe aus. Meldungen gehen auf die
|
||||
// Fehlerausgabe, damit das aufrufende Skript nur das Token einliest.
|
||||
// Aufruf: node einrichten.mjs http://localhost:18123
|
||||
|
||||
const BASIS = process.argv[2] ?? "http://localhost:18123"
|
||||
const CLIENT_ID = BASIS + "/"
|
||||
const BENUTZER = "test"
|
||||
const PASSWORT = "testtest123"
|
||||
|
||||
const melden = (text) => process.stderr.write(` ${text}\n`)
|
||||
|
||||
async function jsonPost(pfad, koerper, token) {
|
||||
const kopf = { "Content-Type": "application/json" }
|
||||
if (token) kopf.Authorization = `Bearer ${token}`
|
||||
const antwort = await fetch(BASIS + pfad, {
|
||||
method: "POST",
|
||||
headers: kopf,
|
||||
body: JSON.stringify(koerper),
|
||||
})
|
||||
const text = await antwort.text()
|
||||
if (!antwort.ok) throw new Error(`${pfad} -> ${antwort.status}: ${text.slice(0, 200)}`)
|
||||
return text ? JSON.parse(text) : {}
|
||||
}
|
||||
|
||||
// 1. Onboarding-Schritt "user"
|
||||
const { auth_code: authCode } = await jsonPost("/api/onboarding/users", {
|
||||
client_id: CLIENT_ID,
|
||||
name: "Test Nutzer",
|
||||
username: BENUTZER,
|
||||
password: PASSWORT,
|
||||
language: "de",
|
||||
})
|
||||
melden("Nutzer angelegt")
|
||||
|
||||
// 2. Auth-Code gegen ein kurzlebiges Zugriffstoken tauschen
|
||||
const tokenAntwort = await fetch(BASIS + "/auth/token", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
code: authCode,
|
||||
client_id: CLIENT_ID,
|
||||
}).toString(),
|
||||
})
|
||||
const token = await tokenAntwort.json()
|
||||
if (!token.access_token) throw new Error("kein access_token: " + JSON.stringify(token))
|
||||
melden("Zugriffstoken erhalten")
|
||||
|
||||
// 3. Restliche Onboarding-Schritte abschliessen (Fehler sind unkritisch)
|
||||
for (const [pfad, koerper] of [
|
||||
["/api/onboarding/core_config", {}],
|
||||
["/api/onboarding/analytics", {}],
|
||||
["/api/onboarding/integration", { client_id: CLIENT_ID, redirect_uri: CLIENT_ID }],
|
||||
]) {
|
||||
try {
|
||||
await jsonPost(pfad, koerper, token.access_token)
|
||||
} catch {
|
||||
// Schritt bereits erledigt oder in dieser Version nicht vorhanden
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Langlebiges Token ueber die WebSocket-API erzeugen
|
||||
const wsUrl = BASIS.replace(/^http/, "ws") + "/api/websocket"
|
||||
const llat = await new Promise((aufloesen, ablehnen) => {
|
||||
const ws = new WebSocket(wsUrl)
|
||||
const zeitgeber = setTimeout(() => {
|
||||
ablehnen(new Error("Zeitueberschreitung beim Erzeugen des Tokens"))
|
||||
ws.close()
|
||||
}, 20000)
|
||||
ws.onmessage = (nachricht) => {
|
||||
const daten = JSON.parse(nachricht.data)
|
||||
if (daten.type === "auth_required") {
|
||||
ws.send(JSON.stringify({ type: "auth", access_token: token.access_token }))
|
||||
} else if (daten.type === "auth_ok") {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
id: 1,
|
||||
type: "auth/long_lived_access_token",
|
||||
client_name: `DataMetric360 Test ${Date.now()}`,
|
||||
lifespan: 365,
|
||||
}),
|
||||
)
|
||||
} else if (daten.type === "result") {
|
||||
clearTimeout(zeitgeber)
|
||||
if (daten.success) aufloesen(daten.result)
|
||||
else ablehnen(new Error(JSON.stringify(daten.error)))
|
||||
ws.close()
|
||||
} else if (daten.type === "auth_invalid") {
|
||||
clearTimeout(zeitgeber)
|
||||
ablehnen(new Error("auth_invalid"))
|
||||
ws.close()
|
||||
}
|
||||
}
|
||||
ws.onerror = () => {
|
||||
clearTimeout(zeitgeber)
|
||||
ablehnen(new Error("WebSocket-Verbindung fehlgeschlagen"))
|
||||
}
|
||||
})
|
||||
melden("langlebiges Token erzeugt")
|
||||
|
||||
process.stdout.write(llat)
|
||||
Reference in New Issue
Block a user