Show normal usage fees separately from the unspecified desire-match surcharge. Preserve free reception and omit guest pricing from warehouse and cleaning rooms. Validation: 155 suites / 12,310 checks; focused 700 checks; strict state and event equivalence; 16 normal and compact FRONT/BACK captures.
218 lines
14 KiB
GDScript
218 lines
14 KiB
GDScript
extends SceneTree
|
|
|
|
## Uses the real Main business controls and Runtime paid-service boundary.
|
|
## No amount, deficit threshold or production attribute assignment is introduced.
|
|
var _checks := 0
|
|
var _failures := 0
|
|
var _runtime: Variant
|
|
var _clock: Variant
|
|
var _main: Control
|
|
var _panel: BusinessManagementPanel
|
|
var _room_id: StringName = &""
|
|
var _workplace_id: StringName = &""
|
|
var _reception_id: StringName = &""
|
|
var _warehouse_id: StringName = &""
|
|
var _cleaning_id: StringName = &""
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
_runtime = root.get_node("SimulationRuntime")
|
|
_clock = root.get_node("GameClock")
|
|
var state: Variant = root.get_node("GameState")
|
|
_clock.reset(1, 6, 0)
|
|
state.reset()
|
|
_runtime.initialize(_clock, state, 42602)
|
|
_clock.set_paused(true)
|
|
root.size = Vector2i(1280, 720)
|
|
_main = load("res://scenes/main/main.tscn").instantiate() as Control
|
|
_main.set("manual_save_path", "user://tests/pending_service_pricing_%d/manual" % OS.get_process_id())
|
|
root.add_child(_main)
|
|
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
|
await process_frame
|
|
await process_frame
|
|
_panel = _main.find_child("BusinessManagementPanel", true, false) as BusinessManagementPanel
|
|
_expect(_panel != null, "normal Main includes business controls")
|
|
if _panel == null:
|
|
_finish()
|
|
return
|
|
# Existing Anri fixture plus one paid cleaning room for non-customer UI.
|
|
for row: Array in [
|
|
[&"reception", Vector3i(-6, 0, 0)],
|
|
[&"warehouse", Vector3i(-6, 0, 1)],
|
|
[&"premium_room", Vector3i(-6, 2, 0)],
|
|
[&"warehouse", Vector3i(-6, 2, 1)],
|
|
[&"service_stairs", Vector3i(-7, 1, 1)],
|
|
[&"service_stairs", Vector3i(-7, 2, 1)],
|
|
[&"cleaning_room", Vector3i(2, 0, 1)],
|
|
]:
|
|
var built: CommandResult = _runtime.submit_command(&"build_facility", {&"definition_id": row[0], &"origin": row[1]})
|
|
_expect(built.is_success(), "normal paid fixture build %s: %s" % [row[0], built.message])
|
|
if not built.is_success():
|
|
_finish()
|
|
return
|
|
_clock.advance_minutes(_runtime.definition_registry.get_facility(row[0]).build_minutes)
|
|
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
|
if facility.definition_id == &"premium_room":
|
|
_room_id = facility.instance_id
|
|
elif facility.definition_id == &"reception":
|
|
_reception_id = facility.instance_id
|
|
elif facility.definition_id == &"cleaning_room":
|
|
_cleaning_id = facility.instance_id
|
|
elif facility.definition_id == &"warehouse":
|
|
if facility.origin.y == 2:
|
|
_workplace_id = facility.instance_id
|
|
else:
|
|
_warehouse_id = facility.instance_id
|
|
_expect(not _room_id.is_empty() and not _workplace_id.is_empty() and not _reception_id.is_empty() and not _warehouse_id.is_empty() and not _cleaning_id.is_empty(), "real customer and non-customer facilities complete before the report")
|
|
if _room_id.is_empty() or _workplace_id.is_empty() or _reception_id.is_empty() or _warehouse_id.is_empty() or _cleaning_id.is_empty():
|
|
_finish()
|
|
return
|
|
_expect(_runtime.submit_command(&"hire_staff_candidate", {&"candidate_id": &"heroine_anri"}).is_success(), "canonical free Anri remains recruitable")
|
|
var assignment: CommandResult = _runtime.submit_command(&"set_staff_work_assignment", {&"staff_id": &"heroine_anri", &"facility_id": _workplace_id, &"template_id": &"DAY"})
|
|
_expect(assignment.is_success(), "normal accessible workplace accepts Anri placement")
|
|
_expect(_runtime.get_facility_business_price(_room()) == 264, "approved room fee includes existing Anri20 percent")
|
|
_test_owned_pending_report()
|
|
await _test_normal_ui()
|
|
_test_frozen_service_price()
|
|
_finish()
|
|
|
|
|
|
func _test_owned_pending_report() -> void:
|
|
var before: Dictionary = _runtime.create_snapshot()
|
|
var before_bytes := var_to_bytes(before)
|
|
var before_digest := SimulationDigest.compute(before)
|
|
var readmodel: Dictionary = _runtime.get_facility_service_pricing_readmodel(_room())
|
|
_expect(readmodel[&"normal_price"] == _runtime.get_facility_business_price(_room()), "readmodel uses actual existing normal charge")
|
|
_expect(readmodel[&"service_attribute"] == null and readmodel.has(&"deficient_attributes") and readmodel[&"deficient_attributes"] == null, "unassigned service attribute and unassessed deficits remain null")
|
|
_expect(_is_pending(readmodel), "unknown deficiency is neither nonmatch nor zero-G surcharge")
|
|
var report: Dictionary = _runtime.get_business_management_report()
|
|
var row := _row(report, _room_id)
|
|
_expect(not row.is_empty() and row[&"price"] == 264 and row[&"service_pricing"][&"normal_price"] == row[&"price"], "normal business report preserves its price and includes separate pending rule")
|
|
var row_before := row.duplicate(true)
|
|
var second: Dictionary = _runtime.get_facility_service_pricing_readmodel(_room())
|
|
readmodel[&"normal_price"] = -900
|
|
readmodel[&"surcharge"] = 0
|
|
readmodel[&"resolved"] = true
|
|
_expect(_is_pending(second) and second[&"normal_price"] == 264, "independent readmodel does not share mutable caller data")
|
|
_expect(var_to_bytes(row) == var_to_bytes(row_before), "direct readmodel does not alias report readmodel")
|
|
(row[&"service_pricing"] as Dictionary).clear()
|
|
(report[&"facilities"] as Array).clear()
|
|
var fresh_row := _row(_runtime.get_business_management_report(), _room_id)
|
|
_expect(_is_pending(fresh_row[&"service_pricing"]) and fresh_row[&"price"] == 264, "mutating nested report output cannot alter subsequent reports")
|
|
var reception: FacilityInstance = _runtime.facility_service.get_instance(_reception_id)
|
|
var free_readmodel: Dictionary = _runtime.get_facility_service_pricing_readmodel(reception)
|
|
_expect(free_readmodel[&"normal_price"] == 0 and _is_pending(free_readmodel), "existing free normal fee does not settle extra rule to zero")
|
|
# These are schema/display examples only, never assigned to production data.
|
|
for attribute: StringName in [&"appetite", &"relaxation", &"sexual", &"gambling"]:
|
|
var named: Dictionary = FacilityPricing.pending_service_readmodel(264, attribute)
|
|
_expect(named[&"service_attribute"] == attribute and named[&"service_attribute"] is StringName and _is_pending(named), "known service label alone cannot resolve a customer match: %s" % attribute)
|
|
var after: Dictionary = _runtime.create_snapshot()
|
|
_expect(var_to_bytes(after) == before_bytes, "report/getter and caller mutations preserve full typed snapshot order and bytes")
|
|
_expect(SimulationDigest.compute(after) == before_digest, "observation preserves values including RNG, IDs and all saved domain events")
|
|
|
|
|
|
func _test_normal_ui() -> void:
|
|
_main.call("_activate_management_mode", &"business")
|
|
_panel.select_facility(_room_id)
|
|
await process_frame
|
|
await process_frame
|
|
_expect(_panel.visible and _panel.price_option.item_count == 5 and not _panel.price_option.disabled, "normal business UI retains five existing price policies")
|
|
var text := _panel.quote_label.text
|
|
_expect(text.contains("通常料金") and text.count("設定後 264G") == 1 and text.count("現在 264G") == 1 and text.count("標準 220G") == 1, "current, draft and base normal fees are each shown once")
|
|
_expect(text.contains("サービス属性:未設定") and text.contains("欲求一致加算:未設定"), "UI identifies both unset extra-rule parts separately from normal charge")
|
|
_expect(not text.contains("加算0G") and not text.contains("利用不可") and not text.contains("resolved"), "pending UI neither invents zero nor blocks use nor exposes implementation terms")
|
|
_expect(_panel.quote_label.autowrap_mode == TextServer.AUTOWRAP_WORD_SMART, "additional status wraps in normal narrow business panel")
|
|
var before := var_to_bytes(_runtime.create_snapshot())
|
|
var owned := _panel.get_report()
|
|
var row := _row(owned, _room_id)
|
|
(row[&"service_pricing"] as Dictionary).clear()
|
|
_panel.refresh()
|
|
_expect(_is_pending(_row(_panel.get_report(), _room_id)[&"service_pricing"]), "panel client-owned report is defensive at nested pricing data")
|
|
for layer in [0, 1]:
|
|
_main.call("_set_layer", layer)
|
|
_panel.refresh()
|
|
_expect(_panel.quote_label.text == text, "palette/layer refresh keeps current normal fee and pending rule unchanged")
|
|
_main.call("_set_layer", 0)
|
|
_panel.select_facility(_reception_id)
|
|
var reception_row := _row(_panel.get_report(), _reception_id)
|
|
_expect(reception_row[&"category"] == &"guest_service" and reception_row[&"price"] == 0 and not bool(reception_row[&"can_price"]), "actual reception is a zero-normal-price guest service")
|
|
_expect(_panel.price_option.disabled and _panel.quote_label.text.count("通常の利用料金はありません(現在 0G)。") == 1 and _panel.quote_label.text.contains("サービス属性:未設定") and _panel.quote_label.text.contains("欲求一致加算:未設定"), "normal reception UI separates free normal fee from unset addition")
|
|
_expect(not _panel.quote_label.text.contains("この施設に利用料金はありません。") and not _panel.quote_label.text.contains("加算0G") and not _panel.business_open_button.disabled, "reception neither claims complete no-fee nor blocks ordinary admission")
|
|
_expect(_is_pending(reception_row[&"service_pricing"]) and reception_row[&"service_pricing"][&"service_attribute"] == null, "reception keeps unknown assessment/attribute/amount owned and unresolved")
|
|
_panel.select_facility(_warehouse_id)
|
|
_expect(_panel.price_option.disabled and _panel.quote_label.text == "この施設に利用料金はありません。" and not _panel.quote_label.text.contains("欲求一致加算"), "non-customer warehouse is not presented as a pending customer charge")
|
|
_expect(_panel.business_open_button.text == "営業する" and _panel.effect_label.text == "営業・休業を設定できます。" and _panel.explanation_label.text.contains("補給"), "warehouse keeps actual supply controls and explanation")
|
|
_panel.select_facility(_cleaning_id)
|
|
_expect(_panel.price_option.disabled and _panel.quote_label.text == "この施設に利用料金はありません。" and not _panel.quote_label.text.contains("欲求一致加算"), "paid cleaning base is not presented as a pending customer charge")
|
|
_expect(_panel.business_open_button.text == "清掃業務を有効にする" and _panel.facility_status_label.text.contains("顧客利用なし") and _panel.effect_label.text == "清掃業務の有効・無効を設定できます。", "cleaning retains staff-work meaning instead of customer admission")
|
|
_panel.select_facility(_room_id)
|
|
_expect(var_to_bytes(_runtime.create_snapshot()) == before, "customer and non-customer business selection/refresh changes no simulation snapshot")
|
|
|
|
|
|
func _test_frozen_service_price() -> void:
|
|
_expect(_runtime.submit_command(&"register_guest_wallet", {&"guest_id": &"pending_rule_payer", &"gold": 1000}).is_success(), "fund controlled normal service payer")
|
|
var queued: CommandResult = _runtime.submit_command(&"create_service_request", {&"request_id": &"pending_rule_stay", &"facility_id": _room_id, &"guest_ids": [&"pending_rule_payer"], &"service_type": &"lodging"})
|
|
_expect(queued.is_success(), "unset extra rule does not stop ordinary service acceptance")
|
|
if not queued.is_success():
|
|
return
|
|
var request: ServiceRequest = _runtime.facility_service.get_request(&"pending_rule_stay")
|
|
_expect(request.quoted_price == 264, "accepted request freezes existing current normal fee")
|
|
var queue_save: Dictionary = _runtime.create_snapshot()
|
|
var changed: CommandResult = _runtime.submit_command(&"set_facility_policy", {&"facility_id": _room_id, &"patch": {&"price_mode": &"expensive"}})
|
|
_expect(changed.is_success() and _runtime.get_facility_business_price(_room()) == 396, "new normal estimate follows existing policy then Anri rounding")
|
|
_expect(_runtime.get_facility_service_pricing_readmodel(_room())[&"normal_price"] == 396 and request.quoted_price == 264, "new readmodel never rewrites an already queued quotation")
|
|
_expect(_runtime.restore_snapshot(queue_save), "queued quote and placement restore through actual Runtime validation")
|
|
request = _runtime.facility_service.get_request(&"pending_rule_stay")
|
|
_expect(request != null and request.quoted_price == 264 and _is_pending(_runtime.get_facility_service_pricing_readmodel(_room())), "loading retains queued normal price without resolving unknown surcharge")
|
|
_clock.advance_one_minute()
|
|
var active: Array = _runtime.facility_service.get_active_services(_room_id)
|
|
_expect(active.size() == 1 and active[0].price_snapshot == 264, "normal service starts with frozen accepted price")
|
|
if active.is_empty():
|
|
return
|
|
var service_id: StringName = active[0].service_id
|
|
var started: Dictionary = _runtime.create_snapshot()
|
|
_expect(_runtime.restore_snapshot(started), "started service restores while extra rule remains pending")
|
|
_expect(_runtime.submit_command(&"set_staff_work_assignment", {&"staff_id": &"heroine_anri", &"facility_id": &"", &"template_id": &"DAY"}).is_success(), "unassign Anri after accepted service has started")
|
|
_expect(_runtime.get_facility_service_pricing_readmodel(_room())[&"normal_price"] == 220 and _runtime.facility_service.get_service(service_id).price_snapshot == 264, "new normal fee reverts but loaded service price stays frozen")
|
|
_clock.advance_minutes(721)
|
|
_expect(_runtime.facility_service.get_service(service_id).state == ServiceInstance.State.COMPLETED, "ordinary paid service completes with unknown extra rule")
|
|
_expect(_runtime.economy_service.get_balance(&"GUEST_WALLET:pending_rule_payer") == 736, "customer pays frozen264 exactly once")
|
|
var revenue := 0
|
|
for entry: LedgerEntry in _runtime.economy_service.get_entries():
|
|
if entry.source_entity_id == _room_id and entry.category == &"SERVICE_SALE":
|
|
revenue += entry.amount
|
|
_expect(revenue == 264 and bool(_runtime.economy_service.validate_ledger()[&"ok"]), "facility receives exactly customer's264 with balanced ledger")
|
|
|
|
|
|
func _is_pending(value: Dictionary) -> bool:
|
|
return value.has(&"resolved") and value[&"resolved"] is bool and value[&"resolved"] == false and value.has(&"deficient_attributes") and value[&"deficient_attributes"] == null and value.has(&"matches") and value[&"matches"] == null and value.has(&"surcharge") and value[&"surcharge"] == null and value.has(&"total") and value[&"total"] == null
|
|
|
|
|
|
func _room() -> FacilityInstance:
|
|
return _runtime.facility_service.get_instance(_room_id)
|
|
|
|
|
|
func _row(report: Dictionary, id: StringName) -> Dictionary:
|
|
for row: Dictionary in report.get(&"facilities", []):
|
|
if StringName(row.get(&"facility_id", &"")) == id:
|
|
return row
|
|
return {}
|
|
|
|
|
|
func _expect(ok: bool, label: String) -> void:
|
|
_checks += 1
|
|
if not ok:
|
|
_failures += 1
|
|
push_error(label)
|
|
|
|
|
|
func _finish() -> void:
|
|
if is_instance_valid(_main):
|
|
_main.queue_free()
|
|
print("Pending service pricing report: %d checks, %d failures" % [_checks, _failures])
|
|
quit(1 if _failures > 0 else 0)
|