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
50 changes: 50 additions & 0 deletions libraries/godot-iap/Example/tests/test_envelope_parsing.gd
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ class FakeImmediateIOSPlugin:
JSON.stringify({"success": true})
)

func restorePurchases() -> String:
return responses.get(
"restorePurchases",
JSON.stringify({"success": true})
)


func _init() -> void:
_run_suite.call_deferred()
Expand Down Expand Up @@ -161,6 +167,7 @@ func _run_all_tests() -> void:
await test_products_fetched_cache()
await test_ios_async_timeout_and_late_callback()
await test_ios_async_disconnect_and_concurrency()
await test_ios_restore_failure_emits_purchase_error()
test_android_signal_handlers_parse_json()

# Android JSON envelopes
Expand Down Expand Up @@ -564,6 +571,49 @@ func test_android_request_purchase_success_envelope() -> void:
_uninstall_fake()


## The non-iOS restore path reports failures through purchase_error. The iOS
## path used to return success = false silently, so a caller listening only to
## the signal saw Android restore failures but never iOS ones.
func test_ios_restore_failure_emits_purchase_error() -> void:
var previous_platform = GodotIapPlugin._platform
var previous_plugin = GodotIapPlugin._native_plugin
GodotIapPlugin._ios_async_results.clear()
GodotIapPlugin._ios_async_result_order.clear()
GodotIapPlugin._ios_async_terminal_keys.clear()
GodotIapPlugin._ios_async_terminal_order.clear()

var errors: Array[Dictionary] = []
var capture_error = func(error: Dictionary) -> void:
errors.append(error)
GodotIapPlugin.purchase_error.connect(capture_error)

# The fake returns its payload without a requestId, so `_call_ios_async`
# takes the immediate-payload path and `restore_purchases()` runs its real
# iOS branch rather than the test re-implementing it.
var fake = _install_ios_fake()
fake.responses["restorePurchases"] = JSON.stringify({
"success": false,
"code": "network-error",
"error": "Restore failed while offline",
})

var ios_result = await GodotIapPlugin.restore_purchases()

_assert_false(ios_result.success, "A rejected iOS restore should not report success")
_assert_equal(errors.size(), 1, "A failed iOS restore should emit purchase_error")
_assert_equal(errors[0].get("code"), "network-error", "The native restore error code should be preserved")
_assert_equal(
errors[0].get("message"),
"Restore failed while offline",
"The native restore error message should be preserved"
)

GodotIapPlugin.purchase_error.disconnect(capture_error)
_uninstall_fake()
GodotIapPlugin._platform = previous_platform
GodotIapPlugin._native_plugin = previous_plugin


func test_android_request_purchase_error_envelope() -> void:
var fake = _install_android_fake()
fake.responses["requestPurchase"] = JSON.stringify({
Expand Down
5 changes: 4 additions & 1 deletion libraries/godot-iap/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,10 @@ macos-build:
# Build Android plugin
android: setup gradle-wrapper
@echo "$(GREEN)Building Android plugin...$(NC)"
@cd $(ANDROID_DIR) && ./gradlew copyReleaseAarToAddons
@# export-android runs --export-debug, and the export plugin embeds the
@# debug AAR for debug exports. Refresh BOTH variants so a debug export can
@# never ship a stale plugin while only the release AAR gets rebuilt.
@cd $(ANDROID_DIR) && ./gradlew copyDebugAarToAddons copyReleaseAarToAddons
@echo "$(GREEN)Generating GDAP file...$(NC)"
@$(PROJECT_ROOT)/scripts/write-gdap.sh "$(ADDON_DIR)/android/GodotIap.gdap"
@echo "$(GREEN)✓ Android plugin built$(NC)"
Expand Down
Binary file modified libraries/godot-iap/addons/godot-iap/android/GodotIap.debug.aar
Binary file not shown.