@isTest private class DirectWebhookDispatcherTest { @isTest static void unmappedUrl_returnsFalse_leavesContextUntouched() { RestRequest sentinelReq = new RestRequest(); RestResponse sentinelRes = new RestResponse(); RestContext.request = sentinelReq; RestContext.response = sentinelRes; Boolean handled = DirectWebhookDispatcher.dispatch( 'HIC_ShopifySync/SomeFutureObjectTypeNotYetWired', '{}', 'Default', 'FutureType'); System.assertEquals(false, handled, 'an unmapped urlMapping must fall back to the HTTP relay'); System.assertEquals(sentinelReq, RestContext.request, 'must not touch RestContext when unmapped'); System.assertEquals(sentinelRes, RestContext.response, 'must not touch RestContext when unmapped'); } @isTest static void blankUrl_returnsFalse() { System.assertEquals(false, DirectWebhookDispatcher.dispatch(null, '{}', 'Default', 'Customer')); System.assertEquals(false, DirectWebhookDispatcher.dispatch('', '{}', 'Default', 'Customer')); } @isTest static void successfulDispatch_handlesInProcess_andRestoresOuterContext() { RestRequest outerReq = new RestRequest(); RestResponse outerRes = new RestResponse(); RestContext.request = outerReq; RestContext.response = outerRes; // No HIC_Configuration__c row for 'Default' is seeded, so the handler takes its safe // no-configuration branch (logs and returns 'OK') rather than attempting real field mapping — // this test is about the DISPATCHER's contract, not the handler's business logic. Boolean handled = DirectWebhookDispatcher.dispatch( 'HIC_ShopifySync/UpdateCustomerFromShopify', '{"id":123456}', 'Default', 'Customer'); System.assertEquals(true, handled, 'a mapped urlMapping must be handled in-process'); System.assertEquals(outerReq, RestContext.request, 'the outer (real) RestRequest must be restored after in-process dispatch'); System.assertEquals(outerRes, RestContext.response, 'the outer (real) RestResponse must be restored after in-process dispatch'); } @isTest static void handlerThrows_isHandled_outerResponseMarkedRetryable_contextRestored() { RestRequest outerReq = new RestRequest(); RestResponse outerRes = new RestResponse(); RestContext.request = outerReq; RestContext.response = outerRes; // Blank body -> the handler's JSON.deserializeUntyped('') throws before it reads any // configuration, exercising the dispatcher's catch/finally path deterministically. Boolean handled = DirectWebhookDispatcher.dispatch( 'HIC_ShopifySync/UpdateCustomerFromShopify', '', 'Default', 'Customer'); System.assertEquals(true, handled, 'a handler exception must still be reported as handled — the caller must not ALSO relay over HTTP'); System.assertEquals(503, outerRes.statusCode, 'a failed in-process dispatch must ask for redelivery on the OUTER (real) response'); System.assertEquals(outerReq, RestContext.request, 'context must be restored even after an exception'); System.assertEquals(outerRes, RestContext.response, 'context must be restored even after an exception'); } @isTest static void deleteCustomer_dispatchesInProcess() { RestContext.request = new RestRequest(); RestContext.response = new RestResponse(); Boolean handled = DirectWebhookDispatcher.dispatch( 'HIC_ShopifySync/DeleteCustomerFromShopify', '{"id":123456}', 'Default', 'Customer'); System.assertEquals(true, handled); } @isTest static void updateLocation_dispatchesInProcess() { RestContext.request = new RestRequest(); RestContext.response = new RestResponse(); Boolean handled = DirectWebhookDispatcher.dispatch( 'HIC_ShopifySync/UpdateLocationFromShopify', '{"id":789}', 'Default', 'HIC_ShopifySync__Location__c'); System.assertEquals(true, handled); } }