OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / _probe_identity_flow_live.gd

133 lines · gd ref:
extends SceneTree
## LIVE probe for IdentityFlow (hyper-games #89): a REAL keet-daemon on scratch
## storage over the TCP transport (Windows' path), driven through a real
## KeetUdsClient — first run (name + vault), recovery phrase, restore.
##
## Needs a scratch daemon (see the #89 comment for the harness) and:
##   HYPERTPS_DAEMON_TCP=127.0.0.1:<port> HYPERTPS_DAEMON_TOKEN=<token>
##   LIVE_RESTORE_PHRASE=<mnemonic> LIVE_RESTORE_MEMBER_ID=<its z32 memberId>
## NEVER point it at a real identity's daemon: step 4 switches identity.

const IdentityFlow := preload("res://scenes/game/identity_flow.gd")

var ok := true
var _client: Node
var _flow: Node
var _reply: Variant = null
var _err: Variant = null
var _got := false


func _p(c: bool, m: String) -> void:
	print(("ok   " if c else "FAIL: ") + m)
	if not c:
		ok = false


func _init() -> void:
	_main.call_deferred()


func _on_reply(result: Variant, err: Variant) -> void:
	_reply = result
	_err = err
	_got = true


func _rpc(method: String, params: Dictionary = {}) -> Variant:
	_got = false
	_client.call_rpc(method, params, _on_reply, 180.0)
	while not _got:
		await process_frame
	return _reply


func _wait_until(cond: Callable, seconds: float) -> bool:
	var end := Time.get_ticks_msec() + int(seconds * 1000.0)
	while Time.get_ticks_msec() < end:
		if cond.call():
			return true
		await process_frame
	return cond.call()


func _form_closed() -> bool:
	return _flow.form.is_empty()


func _form_has_ask() -> bool:
	return _flow.form.has("ask")


func _form_shown() -> bool:
	return _flow.form.get("shown", false)


func _connected() -> bool:
	return bool(_client.get("is_connected"))


func _main() -> void:
	_client = KeetUdsClient.new()
	root.add_child(_client)
	_p(await _wait_until(_connected, 20.0), "connected to the scratch daemon over TCP")
	_flow = IdentityFlow.new()
	_flow.client = _client
	_flow.interactive = true
	root.add_child(_flow)

	# 1. First run.
	var before: Dictionary = await _rpc("identity")
	var member_before := str(before.get("memberId", ""))
	_p(member_before != "" and before.get("displayName") in [null, ""], "fresh identity %s has no name (displayName=%s)" % [member_before, str(before.get("displayName"))])
	_flow.check_first_run(before)
	_p(await _wait_until(func(): return _flow.form.get("kind", "") == "first_run", 20.0), "first-run step open")
	_flow.form.inputs.name.text = "livetester"
	_flow.form.inputs.password.text = "live password 1"
	_flow.form.inputs.confirm.text = "live password 1"
	_flow.form.dlg.confirmed.emit()
	_p(await _wait_until(_form_closed, 200.0), "first run completed (status: %s)" % ("" if _flow.form.is_empty() else _flow.form.status.text))
	var after: Dictionary = await _rpc("identity")
	_p(str(after.get("displayName", "")) == "livetester", "keet now says displayName=livetester (got %s)" % str(after.get("displayName")))
	_p(str(after.get("memberId", "")) == member_before, "same identity after the vault backup (%s)" % str(after.get("memberId")))
	var st: Dictionary = await _rpc("vault.status")
	_p(bool(st.get("exists", false)), "vault written")

	# 2. Recovery phrase.
	_flow.show_phrase()
	_flow.form.inputs.password.text = "live password 1"
	_flow.form.dlg.confirmed.emit()
	_p(await _wait_until(_form_shown, 120.0), "phrase shown")
	var words: String = _flow.form.inputs.phrase.text if _flow.form.inputs.has("phrase") else ""
	_p(words.split(" ").size() == 24, "24 words (%d)" % words.split(" ").size())
	_flow.form.dlg.confirmed.emit()

	# 3. Wrong password says so.
	_flow.show_phrase()
	_flow.form.inputs.password.text = "not the password"
	_flow.form.dlg.confirmed.emit()
	_p(await _wait_until(func(): return _flow.form.status.text == "Wrong password.", 120.0), "wrong password refused: %s" % _flow.form.status.text)
	_flow._close()

	# 4. Restore.
	var phrase := OS.get_environment("LIVE_RESTORE_PHRASE")
	var expect := OS.get_environment("LIVE_RESTORE_MEMBER_ID")
	_flow.restore()
	_flow.form.inputs.phrase.text = phrase
	_flow.form.inputs.password.text = "live password 2"
	_flow.form.inputs.confirm.text = "live password 2"
	_flow.form.dlg.confirmed.emit()
	_p(await _wait_until(_form_has_ask, 5.0), "switch confirmation asked")
	_flow.form.ask.confirmed.emit()
	_p(await _wait_until(_form_closed, 200.0), "restore completed (status: %s)" % ("" if _flow.form.is_empty() else _flow.form.status.text))
	# The swap drops the connection's subscriptions; give the client a moment.
	await _wait_until(_connected, 20.0)
	var restored: Dictionary = await _rpc("identity")
	_p(str(restored.get("memberId", "")) == expect, "identity is now the phrase's (%s, expected %s)" % [str(restored.get("memberId")), expect])
	var list: Variant = await _rpc("identity-manager.list")
	var ids: Array = list.get("identities", []) if list is Dictionary else []
	_p(ids.size() == 2, "the original identity is still on this device (%d identities)" % ids.size())

	print("PROBE identity_flow_live: %s" % ("PASS" if ok else "FAIL"))
	quit(0 if ok else 1)