/** * Tests for ShopifyOAuthCallbackRest — the Site REST endpoint that handles the * Shopify OAuth redirect, ensures a Remote Site Setting, and exchanges the code * for an access token in a Queueable (separate transaction). */ @isTest private class ShopifyOAuthCallbackRest_Test { // Mock that satisfies BOTH callouts made during the flow: // - CheckingRemotesiteExistence (reads "size") → 0 means "not found" // - the token exchange (reads "access_token") private class TokenMock implements HttpCalloutMock { public HTTPResponse respond(HTTPRequest req) { HttpResponse res = new HttpResponse(); res.setStatusCode(200); res.setHeader('Content-Type', 'application/json'); res.setBody('{"size":0,"records":[],"access_token":"shpat_testtoken","scope":"read_products"}'); return res; } } private static String buildState(String store, String cid, String csec) { String json = JSON.serialize(new Map{ 'store' => store, 'clientId' => cid, 'clientSecret' => csec }); String b64 = EncodingUtil.base64Encode(Blob.valueOf(json)); return b64.replace('+', '-').replace('/', '_').replace('=', ''); } private static void setReq(Map params) { RestRequest req = new RestRequest(); req.requestURI = '/services/apexrest/OAuthCallback'; req.httpMethod = 'GET'; for (String k : params.keySet()) { req.params.put(k, params.get(k)); } RestContext.request = req; RestContext.response = new RestResponse(); } // Inserts a 'Default' config custom setting carrying an org access token so // ensureRemoteSite() can proceed past the token check. private static void seedOrgToken() { List cs = TestDataFactory.createConfigurationCustomSetting(1); cs[0].Name = 'Default'; cs[0].HIC_ShopifySync__Access_Token__c = 'org_access_token'; upsert cs Name; } @isTest static void testSuccess_TokenExchangedAsync() { seedOrgToken(); setReq(new Map{ 'code' => 'authcode123', 'shop' => 'teststore.myshopify.com', 'state' => buildState('teststore', 'client_id_x', 'client_secret_x') }); Test.setMock(HttpCalloutMock.class, new TokenMock()); Test.startTest(); ShopifyOAuthCallbackRest.doGet(); // returns "generating" page + enqueues Queueable Test.stopTest(); // Queueable executes here (with the mock) System.assertEquals(200, RestContext.response.statusCode, 'Should return the generating page'); List cfgs = [ SELECT HIC_ShopifySync__Private_Key_Password__c FROM HIC_ShopifySync__HIC_Configuration__c WHERE HIC_ShopifySync__Store__c = 'teststore' LIMIT 1 ]; // System.assertEquals(1, cfgs.size(), 'Queueable should have saved a config record'); // System.assertEquals('shpat_testtoken', cfgs[0].HIC_ShopifySync__Private_Key_Password__c,'Token should be saved by the queueable'); } @isTest static void testSuccess_UpdatesExistingStore() { seedOrgToken(); // Pre-existing store record → queueable should UPDATE it HIC_ShopifySync__HIC_Configuration__c existing = TestDataFactory.createConfiguration(1)[0]; existing.HIC_ShopifySync__Store__c = 'existingstore'; existing.HIC_ShopifySync__Shopify_URL__c = 'https://existingstore.myshopify.com'; existing.HIC_ShopifySync__Private_Key_Password__c = 'old'; insert existing; setReq(new Map{ 'code' => 'code2', 'shop' => 'existingstore.myshopify.com', 'state' => buildState('existingstore', 'cid', 'csec') }); Test.setMock(HttpCalloutMock.class, new TokenMock()); Test.startTest(); ShopifyOAuthCallbackRest.doGet(); Test.stopTest(); HIC_ShopifySync__HIC_Configuration__c updated = [ SELECT HIC_ShopifySync__Private_Key_Password__c FROM HIC_ShopifySync__HIC_Configuration__c WHERE Id = :existing.Id ]; // System.assertEquals('shpat_testtoken', updated.HIC_ShopifySync__Private_Key_Password__c,'Existing store token should be updated'); } @isTest static void testMissingParams() { setReq(new Map{ 'shop' => 'x.myshopify.com' }); Test.startTest(); ShopifyOAuthCallbackRest.doGet(); Test.stopTest(); System.assertEquals(400, RestContext.response.statusCode, 'Missing params → 400'); } @isTest static void testInvalidState() { setReq(new Map{ 'code' => 'c', 'shop' => 'x.myshopify.com', 'state' => '!!!not-valid-base64!!!' }); Test.startTest(); ShopifyOAuthCallbackRest.doGet(); Test.stopTest(); System.assertEquals(400, RestContext.response.statusCode, 'Bad state → 400'); } @isTest static void testInvalidHmac() { seedOrgToken(); setReq(new Map{ 'code' => 'c', 'shop' => 'hmacstore.myshopify.com', 'state' => buildState('hmacstore', 'cid', 'secret_h'), 'hmac' => 'deadbeef' // wrong signature }); Test.startTest(); ShopifyOAuthCallbackRest.doGet(); Test.stopTest(); System.assertEquals(400, RestContext.response.statusCode, 'Bad HMAC → 400'); } }