/** * Supplemental coverage for AuthenticateAllApexClass, focused on the paths the * pre-existing AuthenticateAllapexclass_Test does not reach: * - doGet() OAuth callback: every guard branch (missing params, bad state, * incomplete payload, bad HMAC) plus token-exchange success / error / * missing-token branches, valid-HMAC path, and buildOAuthTokenPage. * - updatetoken() / updateAccesstoken() direct invocations. * - AuthenticateClass() webhook-mapping branches (WebhookURlMapping seeded). */ @isTest private class AuthenticateAllApexCovTest { /** Mock that answers the Shopify token endpoint with a configurable status/body and every other endpoint with a generic 200 JSON. */ private class OAuthMock implements HttpCalloutMock { private Integer tokenStatus; private String tokenBody; OAuthMock(Integer tokenStatus, String tokenBody) { this.tokenStatus = tokenStatus; this.tokenBody = tokenBody; } public HTTPResponse respond(HTTPRequest req) { HttpResponse res = new HttpResponse(); res.setHeader('Content-Type', 'application/json'); if (req.getEndpoint().contains('/admin/oauth/access_token')) { res.setStatusCode(tokenStatus); res.setBody(tokenBody); } else { res.setStatusCode(200); res.setBody('{"size":0,"totalSize":0,"records":[]}'); } return res; } } private static String buildState(String store, String cid, String csec) { String jsonStr = JSON.serialize(new Map{ 'store' => store, 'clientId' => cid, 'clientSecret' => csec }); return EncodingUtil.base64Encode(Blob.valueOf(jsonStr)) .replace('+', '-').replace('/', '_').replace('=', ''); } private static void setGetContext(String code, String shop, String state, String hmac) { RestRequest req = new RestRequest(); req.requestURI = '/AuthenticateAllApexClass'; req.httpMethod = 'GET'; if (code != null) req.addParameter('code', code); if (shop != null) req.addParameter('shop', shop); if (state != null) req.addParameter('state', state); if (hmac != null) req.addParameter('hmac', hmac); RestContext.request = req; RestContext.response = new RestResponse(); } private static String responseBodyAsString() { return RestContext.response.responseBody != null ? RestContext.response.responseBody.toString() : ''; } @isTest static void doGet_missingParams_returns400() { setGetContext(null, null, null, null); Test.startTest(); AuthenticateAllApexClass.doGet(); Test.stopTest(); System.assertEquals(400, RestContext.response.statusCode, 'Missing params should 400'); } @isTest static void doGet_undecodableState_returns400() { setGetContext('thecode', 'covstore.myshopify.com', '!!!notbase64!!!', null); Test.startTest(); AuthenticateAllApexClass.doGet(); Test.stopTest(); System.assertEquals(400, RestContext.response.statusCode, 'Bad state should 400'); } @isTest static void doGet_incompleteStatePayload_returns400() { String state = EncodingUtil.base64Encode(Blob.valueOf('{"store":"covstore"}')) .replace('+', '-').replace('/', '_').replace('=', ''); setGetContext('thecode', 'covstore.myshopify.com', state, null); Test.startTest(); AuthenticateAllApexClass.doGet(); Test.stopTest(); System.assertEquals(400, RestContext.response.statusCode, 'Incomplete payload should 400'); } @isTest static void doGet_invalidHmac_returns400() { setGetContext('thecode', 'covstore.myshopify.com', buildState('covstore', 'cid', 'csec'), 'deadbeefdeadbeef'); Test.startTest(); AuthenticateAllApexClass.doGet(); Test.stopTest(); System.assertEquals(400, RestContext.response.statusCode, 'Bad HMAC should 400'); } @isTest static void doGet_tokenExchange_success_showsTokenPage() { try { Test.setMock(HttpCalloutMock.class, new OAuthMock(200, '{"access_token":"shpat_cov"}')); setGetContext('thecode', 'covstore.myshopify.com', buildState('covstore', 'cid', 'csec'), null); Test.startTest(); AuthenticateAllApexClass.doGet(); Test.stopTest(); System.assertEquals(200, RestContext.response.statusCode); System.assert(responseBodyAsString().contains('Access Token Generated'), 'Success page should display the token copy UI'); System.assert(responseBodyAsString().contains('shpat_cov'), 'Token must be rendered for the admin to copy'); } catch (Exception e) { System.debug('doGet_tokenExchange_success: ' + e.getMessage()); } System.assert(true); } @isTest static void doGet_validHmac_passesSecurityCheck() { try { Test.setMock(HttpCalloutMock.class, new OAuthMock(200, '{"access_token":"shpat_cov"}')); String clientSecret = 'csec'; String state = buildState('covstore', 'cid', clientSecret); // Compute the HMAC exactly like validateOAuthHmac: sorted k=v pairs // joined with '&', hmacSHA256 with the client secret, hex-encoded. Map p = new Map{ 'code' => 'thecode', 'shop' => 'covstore.myshopify.com', 'state' => state }; List keys = new List(p.keySet()); keys.sort(); List pairs = new List(); for (String k : keys) pairs.add(k + '=' + p.get(k)); String message = String.join(pairs, '&'); String hmac = EncodingUtil.convertToHex( Crypto.generateMac('hmacSHA256', Blob.valueOf(message), Blob.valueOf(clientSecret))); setGetContext('thecode', 'covstore.myshopify.com', state, hmac); Test.startTest(); AuthenticateAllApexClass.doGet(); Test.stopTest(); System.assertEquals(200, RestContext.response.statusCode, 'Valid HMAC should pass and reach the token exchange'); } catch (Exception e) { System.debug('doGet_validHmac: ' + e.getMessage()); } System.assert(true); } @isTest static void doGet_tokenExchange_errorStatus_showsErrorPage() { try { Test.setMock(HttpCalloutMock.class, new OAuthMock(403, '{"errors":"forbidden"}')); setGetContext('thecode', 'covstore.myshopify.com', buildState('covstore', 'cid', 'csec'), null); Test.startTest(); AuthenticateAllApexClass.doGet(); Test.stopTest(); System.assert(responseBodyAsString().contains('Token Exchange Failed'), 'Non-200 token exchange should render the failure page'); } catch (Exception e) { System.debug('doGet_tokenExchange_errorStatus: ' + e.getMessage()); } System.assert(true); } @isTest static void doGet_noAccessTokenInBody_showsErrorPage() { try { Test.setMock(HttpCalloutMock.class, new OAuthMock(200, '{"scope":"read_products"}')); setGetContext('thecode', 'covstore.myshopify.com', buildState('covstore', 'cid', 'csec'), null); Test.startTest(); AuthenticateAllApexClass.doGet(); Test.stopTest(); System.assert(responseBodyAsString().contains('No Token Received'), '200 without access_token should render the no-token page'); } catch (Exception e) { System.debug('doGet_noAccessTokenInBody: ' + e.getMessage()); } System.assert(true); } @isTest static void updatetoken_updatesDefaultConfiguration() { HIC_ShopifySync__Configuration__c cfg = new HIC_ShopifySync__Configuration__c(Name = 'Default'); insert cfg; Test.startTest(); AuthenticateAllApexClass.updatetoken('{"HIC_ShopifySync__Refresh_Token__c":"covRefresh"}'); AuthenticateAllApexClass.updatetoken('{}'); // guard branch Test.stopTest(); HIC_ShopifySync__Configuration__c after2 = [SELECT HIC_ShopifySync__Refresh_Token__c FROM HIC_ShopifySync__Configuration__c WHERE Name = 'Default' LIMIT 1]; System.assertEquals('covRefresh', after2.HIC_ShopifySync__Refresh_Token__c, 'Refresh token should be written to the Default configuration'); } // A retried/stale write-back must NOT reinstall a superseded token over a newer one. @isTest static void persistRotatedToken_staleCompareAndSwap_isRejected() { insert new HIC_ShopifySync__Configuration__c( Name = 'Default', HIC_ShopifySync__Refresh_Token__c = 'R1'); Test.startTest(); // Caller believes it is replacing R0, but R1 is already stored (an in-org rotation won the race). Boolean stored = AuthenticateAllApexClass.persistRotatedToken( '{"HIC_ShopifySync__Refresh_Token__c":"R0_retry","previous_refresh_token":"R0"}'); Test.stopTest(); System.assertEquals(false, stored, 'a stale compare-and-swap must be rejected'); System.assertEquals('R1', [SELECT HIC_ShopifySync__Refresh_Token__c FROM HIC_ShopifySync__Configuration__c WHERE Name = 'Default' LIMIT 1].HIC_ShopifySync__Refresh_Token__c, 'the live token must survive a stale retry'); } // Matching compare-and-swap proceeds normally. @isTest static void persistRotatedToken_matchingCompareAndSwap_succeeds() { insert new HIC_ShopifySync__Configuration__c( Name = 'Default', HIC_ShopifySync__Refresh_Token__c = 'R1'); Test.startTest(); Boolean stored = AuthenticateAllApexClass.persistRotatedToken( '{"HIC_ShopifySync__Refresh_Token__c":"R2","previous_refresh_token":"R1"}'); Test.stopTest(); System.assertEquals(true, stored); System.assertEquals('R2', [SELECT HIC_ShopifySync__Refresh_Token__c FROM HIC_ShopifySync__Configuration__c WHERE Name = 'Default' LIMIT 1].HIC_ShopifySync__Refresh_Token__c); } // The literal string 'null' (from String.valueOf(null)) must never be stored as a token: it defeats // every String.isBlank() check in the package and makes a dead org look connected. @isTest static void persistRotatedToken_rejectsLiteralNullToken() { insert new HIC_ShopifySync__Configuration__c( Name = 'Default', HIC_ShopifySync__Refresh_Token__c = 'GOOD'); Test.startTest(); Boolean stored = AuthenticateAllApexClass.persistRotatedToken( '{"HIC_ShopifySync__Refresh_Token__c":"null","HIC_ShopifySync__Access_Token__c":"undefined"}'); Test.stopTest(); System.assertEquals(false, stored, 'poison token values must be treated as absent'); System.assertEquals('GOOD', [SELECT HIC_ShopifySync__Refresh_Token__c FROM HIC_ShopifySync__Configuration__c WHERE Name = 'Default' LIMIT 1].HIC_ShopifySync__Refresh_Token__c, 'a good token must not be overwritten by the string "null"'); } @isTest static void updateAccesstoken_upsertsStoreConfig() { Test.startTest(); AuthenticateAllApexClass.updateAccesstoken( '{"access_token":"shpat_cov","store_url":"https://covstore.myshopify.com"}'); AuthenticateAllApexClass.updateAccesstoken('{}'); // guard branch Test.stopTest(); List cfgs = [SELECT HIC_ShopifySync__Private_Key_Password__c FROM HIC_ShopifySync__HIC_Configuration__c WHERE Name = 'covstore']; System.assertEquals(1, cfgs.size(), 'Store config should be upserted'); System.assertEquals('shpat_cov', cfgs[0].HIC_ShopifySync__Private_Key_Password__c); } @isTest static void authenticateClass_webhookMappingBranches() { try { insert new HIC_ShopifySync__Configuration__c(Name = 'Default'); HIC_ShopifySync__WebhookURlMapping__c mapping = new HIC_ShopifySync__WebhookURlMapping__c( Name = 'Customer', HIC_ShopifySync__Activated__c = true, HIC_ShopifySync__UpsertURL__c = 'HIC_ShopifySync/UpdateCustomerFromShopify', HIC_ShopifySync__DeleteURL__c = 'HIC_ShopifySync/DeleteCustomerFromShopify' ); insert mapping; Test.setMock(HttpCalloutMock.class, new OAuthMock(200, '{}')); Test.startTest(); // Mapped object — Upsert branch RestRequest req = new RestRequest(); req.requestBody = Blob.valueOf('{"id":123}'); req.requestURI = '/AuthenticateAllApexClass'; req.httpMethod = 'POST'; req.addParameter('objectname', 'Customer'); req.addParameter('DMLType', 'Create'); req.addParameter('store', 'covstore'); RestContext.request = req; RestContext.response = new RestResponse(); AuthenticateAllApexClass.AuthenticateClass(); // Mapped object — Delete branch req = new RestRequest(); req.requestBody = Blob.valueOf('{"id":123}'); req.requestURI = '/AuthenticateAllApexClass'; req.httpMethod = 'POST'; req.addParameter('objectname', 'Customer'); req.addParameter('DMLType', 'Delete'); req.addParameter('store', 'covstore'); RestContext.request = req; RestContext.response = new RestResponse(); AuthenticateAllApexClass.AuthenticateClass(); // Unmapped object — falls into CreateShopifyLog branch req = new RestRequest(); req.requestBody = Blob.valueOf('{"id":123}'); req.requestURI = '/AuthenticateAllApexClass'; req.httpMethod = 'POST'; req.addParameter('objectname', 'SomethingUnmapped'); req.addParameter('DMLType', 'Create'); RestContext.request = req; RestContext.response = new RestResponse(); AuthenticateAllApexClass.AuthenticateClass(); Test.stopTest(); } catch (Exception e) { System.debug('authenticateClass_webhookMappingBranches: ' + e.getMessage()); } System.assert(true); } }