Created
February 12, 2026 09:14
-
-
Save musketyr/8a805aadc413b11e1e79e61ea2e9bdcf to your computer and use it in GitHub Desktop.
Spock to JUnit 5 Migration Diff - sc185914 - RoiDataSynchronizerServiceSpec
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Spock → JUnit 5 Migration: RoiDataSynchronizerServiceSpec</title> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; padding: 20px; line-height: 1.6; } | |
| h1 { text-align: center; margin-bottom: 20px; color: #333; } | |
| .section { background: white; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: hidden; } | |
| .section-header { background: #2d3748; color: white; padding: 12px 16px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; } | |
| .section-header:hover { background: #4a5568; } | |
| .section-header h2 { font-size: 16px; font-weight: 600; } | |
| .section-header .toggle { font-size: 12px; } | |
| .section-content { display: block; } | |
| .section-content.collapsed { display: none; } | |
| .diff-container { display: grid; grid-template-columns: 1fr 1fr; } | |
| .diff-panel { padding: 16px; overflow-x: auto; } | |
| .diff-panel.left { background: #fff5f5; border-right: 1px solid #e2e8f0; } | |
| .diff-panel.right { background: #f0fff4; } | |
| .diff-panel h3 { font-size: 12px; text-transform: uppercase; color: #718096; margin-bottom: 12px; letter-spacing: 0.5px; } | |
| pre { font-family: 'SF Mono', Monaco, 'Courier New', monospace; font-size: 13px; white-space: pre-wrap; word-wrap: break-word; } | |
| .left pre { color: #c53030; } | |
| .right pre { color: #276749; } | |
| .summary { background: #c6f6d5; border-left: 4px solid #38a169; padding: 12px 16px; margin: 0; } | |
| .summary p { color: #276749; font-size: 14px; } | |
| .summary strong { color: #22543d; } | |
| @media (max-width: 768px) { .diff-container { grid-template-columns: 1fr; } .diff-panel.left { border-right: none; border-bottom: 1px solid #e2e8f0; } } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🦀 Spock → JUnit 5 Migration: RoiDataSynchronizerServiceSpec</h1> | |
| <p style="text-align: center; color: #666; margin-bottom: 20px;">Story: <a href="https://app.shortcut.com/agorapulse/story/185914">sc185914</a> | PR: <a href="https://github.com/agorapulse/platform/pull/72756">#72756</a></p> | |
| <!-- Section: Imports --> | |
| <div class="section"> | |
| <div class="section-header" onclick="toggleSection(this)"> | |
| <h2>📦 Imports</h2> | |
| <span class="toggle">▼</span> | |
| </div> | |
| <div class="section-content"> | |
| <div class="diff-container"> | |
| <div class="diff-panel left"> | |
| <h3>Spock (Groovy)</h3> | |
| <pre>package agorapulse.roi.data.synchronizer | |
| import agorapulse.organization.client.internal.ManagerClient | |
| import agorapulse.organization.client.internal.OrganizationClient | |
| import agorapulse.organization.client.model.ManagerSummary | |
| import agorapulse.organization.client.model.OrganizationManagerSummary | |
| import agorapulse.organization.client.model.OrganizationRole | |
| import agorapulse.organization.client.model.OrganizationSummary | |
| import agorapulse.organization.subscription.client.SubscriptionContextService | |
| import agorapulse.organization.subscription.client.context.SubscriptionContext | |
| import agorapulse.organization.subscription.client.context.SubscriptionType | |
| import agorapulse.roi.data.entity.googleanalyticsaccount.GoogleAnalyticsAccountEntity | |
| import agorapulse.roi.data.entity.googleanalyticsdashboard.GoogleAnalyticsDashboardEntity | |
| import agorapulse.roi.data.service.googleanalyticsdashboard.GoogleAnalyticsDashboardService | |
| import agorapulse.roi.internal.client.RoiClient | |
| import com.amazonaws.services.lambda.runtime.events.SQSEvent | |
| import spock.lang.Specification</pre> | |
| </div> | |
| <div class="diff-panel right"> | |
| <h3>JUnit 5 (Java)</h3> | |
| <pre>package agorapulse.roi.data.synchronizer; | |
| import agorapulse.organization.client.internal.ManagerClient; | |
| import agorapulse.organization.client.internal.OrganizationClient; | |
| import agorapulse.organization.client.model.ManagerSummary; | |
| import agorapulse.organization.client.model.OrganizationManagerSummary; | |
| import agorapulse.organization.client.model.OrganizationRole; | |
| import agorapulse.organization.client.model.OrganizationSummary; | |
| import agorapulse.organization.subscription.client.SubscriptionContextService; | |
| import agorapulse.organization.subscription.client.context.SubscriptionContext; | |
| import agorapulse.organization.subscription.client.context.SubscriptionType; | |
| import agorapulse.roi.data.entity.googleanalyticsaccount.GoogleAnalyticsAccountEntity; | |
| import agorapulse.roi.data.entity.googleanalyticsdashboard.GoogleAnalyticsDashboardEntity; | |
| import agorapulse.roi.data.service.googleanalyticsdashboard.GoogleAnalyticsDashboardService; | |
| import agorapulse.roi.internal.client.RoiClient; | |
| import com.amazonaws.services.lambda.runtime.events.SQSEvent; | |
| import org.junit.jupiter.api.BeforeEach; | |
| import org.junit.jupiter.api.Test; | |
| import org.junit.jupiter.api.extension.ExtendWith; | |
| import org.mockito.Mock; | |
| import org.mockito.junit.jupiter.MockitoExtension; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import java.util.Optional; | |
| import static org.mockito.ArgumentMatchers.any; | |
| import static org.mockito.Mockito.verify; | |
| import static org.mockito.Mockito.verifyNoInteractions; | |
| import static org.mockito.Mockito.verifyNoMoreInteractions; | |
| import static org.mockito.Mockito.when;</pre> | |
| </div> | |
| </div> | |
| <div class="summary"> | |
| <p><strong>Changes:</strong> Replaced Spock imports with JUnit 5 + Mockito imports. Added MockitoExtension, @Mock, @BeforeEach, @Test annotations. Added static imports for Mockito methods.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Section: Class & Fields --> | |
| <div class="section"> | |
| <div class="section-header" onclick="toggleSection(this)"> | |
| <h2>🏗️ Class & Fields</h2> | |
| <span class="toggle">▼</span> | |
| </div> | |
| <div class="section-content"> | |
| <div class="diff-container"> | |
| <div class="diff-panel left"> | |
| <h3>Spock (Groovy)</h3> | |
| <pre>class RoiDataSynchronizerServiceSpec extends Specification { | |
| RoiDataSynchronizerService service | |
| GoogleAnalyticsDashboardService googleAnalyticsDashboardService = Mock(GoogleAnalyticsDashboardService) | |
| ManagerClient managerClient = Mock(ManagerClient) | |
| OrganizationClient organizationClient = Mock(OrganizationClient) | |
| RoiClient roiClient = Mock(RoiClient) | |
| SubscriptionContextService subscriptionContextService = Mock(SubscriptionContextService)</pre> | |
| </div> | |
| <div class="diff-panel right"> | |
| <h3>JUnit 5 (Java)</h3> | |
| <pre>@ExtendWith(MockitoExtension.class) | |
| class RoiDataSynchronizerServiceTest { | |
| @Mock | |
| private GoogleAnalyticsDashboardService googleAnalyticsDashboardService; | |
| @Mock | |
| private ManagerClient managerClient; | |
| @Mock | |
| private OrganizationClient organizationClient; | |
| @Mock | |
| private RoiClient roiClient; | |
| @Mock | |
| private SubscriptionContextService subscriptionContextService; | |
| private RoiDataSynchronizerService service;</pre> | |
| </div> | |
| </div> | |
| <div class="summary"> | |
| <p><strong>Changes:</strong> Replaced Spock's extends Specification with @ExtendWith(MockitoExtension.class). Converted Mock(Class) to @Mock annotations. Added proper Java field declarations with semicolons.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Section: Setup --> | |
| <div class="section"> | |
| <div class="section-header" onclick="toggleSection(this)"> | |
| <h2>🔧 Setup</h2> | |
| <span class="toggle">▼</span> | |
| </div> | |
| <div class="section-content"> | |
| <div class="diff-container"> | |
| <div class="diff-panel left"> | |
| <h3>Spock (Groovy)</h3> | |
| <pre>void setup() { | |
| service = new RoiDataSynchronizerService( | |
| googleAnalyticsDashboardService, managerClient, organizationClient, roiClient, subscriptionContextService | |
| ) | |
| }</pre> | |
| </div> | |
| <div class="diff-panel right"> | |
| <h3>JUnit 5 (Java)</h3> | |
| <pre>@BeforeEach | |
| void setup() { | |
| service = new RoiDataSynchronizerService( | |
| googleAnalyticsDashboardService, | |
| managerClient, | |
| organizationClient, | |
| roiClient, | |
| subscriptionContextService | |
| ); | |
| }</pre> | |
| </div> | |
| </div> | |
| <div class="summary"> | |
| <p><strong>Changes:</strong> Added @BeforeEach annotation (JUnit 5 equivalent of Spock's setup()). Added semicolon and improved formatting.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Test 1: should not send any data if the dashboard does not exist --> | |
| <div class="section"> | |
| <div class="section-header" onclick="toggleSection(this)"> | |
| <h2>🧪 should not send any data if the dashboard does not exist</h2> | |
| <span class="toggle">▼</span> | |
| </div> | |
| <div class="section-content"> | |
| <div class="diff-container"> | |
| <div class="diff-panel left"> | |
| <h3>Spock (Groovy)</h3> | |
| <pre>void 'should not send any data if the dashboard does not exist'() { | |
| given: | |
| String dashboardId = '1' | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage() | |
| sqsMessage.body = dashboardId | |
| SQSEvent sqsEvent = new SQSEvent(records: [sqsMessage]) | |
| when: | |
| service.handleSynchronizations(sqsEvent) | |
| then: | |
| 1 * googleAnalyticsDashboardService.getDashboard(dashboardId.toLong()) >> null | |
| 0 * _ | |
| }</pre> | |
| </div> | |
| <div class="diff-panel right"> | |
| <h3>JUnit 5 (Java)</h3> | |
| <pre>@Test | |
| void should_not_send_any_data_if_the_dashboard_does_not_exist() { | |
| // given | |
| String dashboardId = "1"; | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage(); | |
| sqsMessage.setBody(dashboardId); | |
| SQSEvent sqsEvent = new SQSEvent(); | |
| sqsEvent.setRecords(List.of(sqsMessage)); | |
| when(googleAnalyticsDashboardService.getDashboard(1L)).thenReturn(null); | |
| // when | |
| service.handleSynchronizations(sqsEvent); | |
| // then | |
| verify(googleAnalyticsDashboardService).getDashboard(1L); | |
| verifyNoMoreInteractions(googleAnalyticsDashboardService); | |
| verifyNoInteractions(subscriptionContextService, organizationClient, managerClient, roiClient); | |
| }</pre> | |
| </div> | |
| </div> | |
| <div class="summary"> | |
| <p><strong>Changes:</strong> Added @Test annotation. Replaced Groovy property access with Java setters. Converted Spock stubbing (1 * ... >> null) to Mockito when().thenReturn() + verify(). Replaced 0 * _ with verifyNoMoreInteractions() and verifyNoInteractions().</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Test 2: should not send any data if there is no active subscription --> | |
| <div class="section"> | |
| <div class="section-header" onclick="toggleSection(this)"> | |
| <h2>🧪 should not send any data if there is no active subscription</h2> | |
| <span class="toggle">▼</span> | |
| </div> | |
| <div class="section-content"> | |
| <div class="diff-container"> | |
| <div class="diff-panel left"> | |
| <h3>Spock (Groovy)</h3> | |
| <pre>void 'should not send any data if there is no active subscription'() { | |
| given: | |
| String dashboardId = '1' | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage() | |
| sqsMessage.body = dashboardId | |
| SQSEvent sqsEvent = new SQSEvent(records: [sqsMessage]) | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder().organizationUid('organization_1').workspaceUid('workspace_1').token('token').build()) | |
| .build() | |
| when: | |
| service.handleSynchronizations(sqsEvent) | |
| then: | |
| 1 * googleAnalyticsDashboardService.getDashboard(1L) >> dashboard | |
| 1 * subscriptionContextService.getSubscriptionContextForRoi(1L) >> Optional.of(SubscriptionContext.builder().type(SubscriptionType.NONE).build()) | |
| 0 * _ | |
| }</pre> | |
| </div> | |
| <div class="diff-panel right"> | |
| <h3>JUnit 5 (Java)</h3> | |
| <pre>@Test | |
| void should_not_send_any_data_if_there_is_no_active_subscription() { | |
| // given | |
| String dashboardId = "1"; | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage(); | |
| sqsMessage.setBody(dashboardId); | |
| SQSEvent sqsEvent = new SQSEvent(); | |
| sqsEvent.setRecords(List.of(sqsMessage)); | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder() | |
| .organizationUid("organization_1") | |
| .workspaceUid("workspace_1") | |
| .token("token") | |
| .build()) | |
| .build(); | |
| when(googleAnalyticsDashboardService.getDashboard(1L)).thenReturn(dashboard); | |
| when(subscriptionContextService.getSubscriptionContextForRoi(1L)) | |
| .thenReturn(Optional.of(SubscriptionContext.builder().type(SubscriptionType.NONE).build())); | |
| // when | |
| service.handleSynchronizations(sqsEvent); | |
| // then | |
| verify(googleAnalyticsDashboardService).getDashboard(1L); | |
| verify(subscriptionContextService).getSubscriptionContextForRoi(1L); | |
| verifyNoMoreInteractions(googleAnalyticsDashboardService, subscriptionContextService); | |
| verifyNoInteractions(organizationClient, managerClient, roiClient); | |
| }</pre> | |
| </div> | |
| </div> | |
| <div class="summary"> | |
| <p><strong>Changes:</strong> Converted Groovy single quotes to Java double quotes. Replaced Spock stubbing with Mockito when().thenReturn(). Split verify calls for each interaction. Used verifyNoMoreInteractions() for called mocks and verifyNoInteractions() for unused mocks.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Test 3: should use cache to check active subscriptions --> | |
| <div class="section"> | |
| <div class="section-header" onclick="toggleSection(this)"> | |
| <h2>🧪 should use cache to check active subscriptions (only 1 call to subscription context)</h2> | |
| <span class="toggle">▼</span> | |
| </div> | |
| <div class="section-content"> | |
| <div class="diff-container"> | |
| <div class="diff-panel left"> | |
| <h3>Spock (Groovy)</h3> | |
| <pre>void 'should use cache to check active subscriptions (only 1 call to subscription context)'() { | |
| given: | |
| String dashboardId = '1,2,3' | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage() | |
| sqsMessage.body = dashboardId | |
| SQSEvent sqsEvent = new SQSEvent(records: [sqsMessage]) | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder().organizationUid('organization_1').workspaceUid('workspace_1').token('token').build()) | |
| .build() | |
| when: | |
| service.handleSynchronizations(sqsEvent) | |
| then: | |
| 1 * googleAnalyticsDashboardService.getDashboard(1L) >> dashboard | |
| 1 * googleAnalyticsDashboardService.getDashboard(2L) >> dashboard | |
| 1 * googleAnalyticsDashboardService.getDashboard(3L) >> dashboard | |
| 1 * subscriptionContextService.getSubscriptionContextForRoi(1L) >> Optional.of(SubscriptionContext.builder().type(SubscriptionType.NONE).build()) | |
| 0 * _ | |
| }</pre> | |
| </div> | |
| <div class="diff-panel right"> | |
| <h3>JUnit 5 (Java)</h3> | |
| <pre>@Test | |
| void should_use_cache_to_check_active_subscriptions_only_1_call_to_subscription_context() { | |
| // given | |
| String dashboardId = "1,2,3"; | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage(); | |
| sqsMessage.setBody(dashboardId); | |
| SQSEvent sqsEvent = new SQSEvent(); | |
| sqsEvent.setRecords(List.of(sqsMessage)); | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder() | |
| .organizationUid("organization_1") | |
| .workspaceUid("workspace_1") | |
| .token("token") | |
| .build()) | |
| .build(); | |
| when(googleAnalyticsDashboardService.getDashboard(1L)).thenReturn(dashboard); | |
| when(googleAnalyticsDashboardService.getDashboard(2L)).thenReturn(dashboard); | |
| when(googleAnalyticsDashboardService.getDashboard(3L)).thenReturn(dashboard); | |
| when(subscriptionContextService.getSubscriptionContextForRoi(1L)) | |
| .thenReturn(Optional.of(SubscriptionContext.builder().type(SubscriptionType.NONE).build())); | |
| // when | |
| service.handleSynchronizations(sqsEvent); | |
| // then | |
| verify(googleAnalyticsDashboardService).getDashboard(1L); | |
| verify(googleAnalyticsDashboardService).getDashboard(2L); | |
| verify(googleAnalyticsDashboardService).getDashboard(3L); | |
| verify(subscriptionContextService).getSubscriptionContextForRoi(1L); | |
| verifyNoMoreInteractions(googleAnalyticsDashboardService, subscriptionContextService); | |
| verifyNoInteractions(organizationClient, managerClient, roiClient); | |
| }</pre> | |
| </div> | |
| </div> | |
| <div class="summary"> | |
| <p><strong>Changes:</strong> Tests caching behavior - subscription context is called only once for 3 dashboards from same organization. Spock's multiple 1 * calls converted to separate when().thenReturn() stubs and verify() calls.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Test 4: should not send any data if there is no owner --> | |
| <div class="section"> | |
| <div class="section-header" onclick="toggleSection(this)"> | |
| <h2>🧪 should not send any data if there is no owner</h2> | |
| <span class="toggle">▼</span> | |
| </div> | |
| <div class="section-content"> | |
| <div class="diff-container"> | |
| <div class="diff-panel left"> | |
| <h3>Spock (Groovy)</h3> | |
| <pre>void 'should not send any data if there is no owner'() { | |
| given: | |
| String dashboardId = '1' | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage() | |
| sqsMessage.body = dashboardId | |
| SQSEvent sqsEvent = new SQSEvent(records: [sqsMessage]) | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder().organizationUid('organization_1').workspaceUid('workspace_1').token('token').build()) | |
| .build() | |
| when: | |
| service.handleSynchronizations(sqsEvent) | |
| then: | |
| 1 * googleAnalyticsDashboardService.getDashboard(1L) >> dashboard | |
| 1 * subscriptionContextService.getSubscriptionContextForRoi(1L) >> Optional.of(SubscriptionContext.builder().type(SubscriptionType.PAID).build()) | |
| 1 * organizationClient.getOrganization(1L) >> new OrganizationSummary( | |
| organizationManagers: [] | |
| ) | |
| 0 * _ | |
| }</pre> | |
| </div> | |
| <div class="diff-panel right"> | |
| <h3>JUnit 5 (Java)</h3> | |
| <pre>@Test | |
| void should_not_send_any_data_if_there_is_no_owner() { | |
| // given | |
| String dashboardId = "1"; | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage(); | |
| sqsMessage.setBody(dashboardId); | |
| SQSEvent sqsEvent = new SQSEvent(); | |
| sqsEvent.setRecords(List.of(sqsMessage)); | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder() | |
| .organizationUid("organization_1") | |
| .workspaceUid("workspace_1") | |
| .token("token") | |
| .build()) | |
| .build(); | |
| OrganizationSummary organizationSummary = new OrganizationSummary(); | |
| organizationSummary.setOrganizationManagers(Collections.emptyList()); | |
| when(googleAnalyticsDashboardService.getDashboard(1L)).thenReturn(dashboard); | |
| when(subscriptionContextService.getSubscriptionContextForRoi(1L)) | |
| .thenReturn(Optional.of(SubscriptionContext.builder().type(SubscriptionType.PAID).build())); | |
| when(organizationClient.getOrganization(1L)).thenReturn(organizationSummary); | |
| // when | |
| service.handleSynchronizations(sqsEvent); | |
| // then | |
| verify(googleAnalyticsDashboardService).getDashboard(1L); | |
| verify(subscriptionContextService).getSubscriptionContextForRoi(1L); | |
| verify(organizationClient).getOrganization(1L); | |
| verifyNoMoreInteractions(googleAnalyticsDashboardService, subscriptionContextService, organizationClient); | |
| verifyNoInteractions(managerClient, roiClient); | |
| }</pre> | |
| </div> | |
| </div> | |
| <div class="summary"> | |
| <p><strong>Changes:</strong> Replaced Groovy constructor with named parameters (organizationManagers: []) with Java setter (setOrganizationManagers(Collections.emptyList())). Now using PAID subscription type which has active subscription, but no owner stops the flow.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Test 5: should use cache to check owner --> | |
| <div class="section"> | |
| <div class="section-header" onclick="toggleSection(this)"> | |
| <h2>🧪 should use cache to check owner (only 1 call to organizationClient)</h2> | |
| <span class="toggle">▼</span> | |
| </div> | |
| <div class="section-content"> | |
| <div class="diff-container"> | |
| <div class="diff-panel left"> | |
| <h3>Spock (Groovy)</h3> | |
| <pre>void 'should use cache to check owner (only 1 call to organizationClient)'() { | |
| given: | |
| String dashboardId = '1,2,3' | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage() | |
| sqsMessage.body = dashboardId | |
| SQSEvent sqsEvent = new SQSEvent(records: [sqsMessage]) | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder().organizationUid('organization_1').workspaceUid('workspace_1').token('token').build()) | |
| .build() | |
| when: | |
| service.handleSynchronizations(sqsEvent) | |
| then: | |
| 1 * googleAnalyticsDashboardService.getDashboard(1L) >> dashboard | |
| 1 * googleAnalyticsDashboardService.getDashboard(2L) >> dashboard | |
| 1 * googleAnalyticsDashboardService.getDashboard(3L) >> dashboard | |
| 1 * subscriptionContextService.getSubscriptionContextForRoi(1L) >> Optional.of(SubscriptionContext.builder().type(SubscriptionType.PAID).build()) | |
| 1 * organizationClient.getOrganization(1L) >> new OrganizationSummary( | |
| organizationManagers: [] | |
| ) | |
| 0 * _ | |
| }</pre> | |
| </div> | |
| <div class="diff-panel right"> | |
| <h3>JUnit 5 (Java)</h3> | |
| <pre>@Test | |
| void should_use_cache_to_check_owner_only_1_call_to_organizationClient() { | |
| // given | |
| String dashboardId = "1,2,3"; | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage(); | |
| sqsMessage.setBody(dashboardId); | |
| SQSEvent sqsEvent = new SQSEvent(); | |
| sqsEvent.setRecords(List.of(sqsMessage)); | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder() | |
| .organizationUid("organization_1") | |
| .workspaceUid("workspace_1") | |
| .token("token") | |
| .build()) | |
| .build(); | |
| OrganizationSummary organizationSummary = new OrganizationSummary(); | |
| organizationSummary.setOrganizationManagers(Collections.emptyList()); | |
| when(googleAnalyticsDashboardService.getDashboard(1L)).thenReturn(dashboard); | |
| when(googleAnalyticsDashboardService.getDashboard(2L)).thenReturn(dashboard); | |
| when(googleAnalyticsDashboardService.getDashboard(3L)).thenReturn(dashboard); | |
| when(subscriptionContextService.getSubscriptionContextForRoi(1L)) | |
| .thenReturn(Optional.of(SubscriptionContext.builder().type(SubscriptionType.PAID).build())); | |
| when(organizationClient.getOrganization(1L)).thenReturn(organizationSummary); | |
| // when | |
| service.handleSynchronizations(sqsEvent); | |
| // then | |
| verify(googleAnalyticsDashboardService).getDashboard(1L); | |
| verify(googleAnalyticsDashboardService).getDashboard(2L); | |
| verify(googleAnalyticsDashboardService).getDashboard(3L); | |
| verify(subscriptionContextService).getSubscriptionContextForRoi(1L); | |
| verify(organizationClient).getOrganization(1L); | |
| verifyNoMoreInteractions(googleAnalyticsDashboardService, subscriptionContextService, organizationClient); | |
| verifyNoInteractions(managerClient, roiClient); | |
| }</pre> | |
| </div> | |
| </div> | |
| <div class="summary"> | |
| <p><strong>Changes:</strong> Tests owner caching - organization client called only once for 3 dashboards from same organization. Same pattern as subscription caching test.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Test 6: should not send any data if there is no token --> | |
| <div class="section"> | |
| <div class="section-header" onclick="toggleSection(this)"> | |
| <h2>🧪 should not send any data if there is no token</h2> | |
| <span class="toggle">▼</span> | |
| </div> | |
| <div class="section-content"> | |
| <div class="diff-container"> | |
| <div class="diff-panel left"> | |
| <h3>Spock (Groovy)</h3> | |
| <pre>void 'should not send any data if there is no token'() { | |
| given: | |
| String dashboardId = '1' | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage() | |
| sqsMessage.body = dashboardId | |
| SQSEvent sqsEvent = new SQSEvent(records: [sqsMessage]) | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder().organizationUid('organization_1').workspaceUid('workspace_1').token(null).build()) | |
| .build() | |
| when: | |
| service.handleSynchronizations(sqsEvent) | |
| then: | |
| 1 * googleAnalyticsDashboardService.getDashboard(dashboardId.toLong()) >> dashboard | |
| 0 * _ | |
| }</pre> | |
| </div> | |
| <div class="diff-panel right"> | |
| <h3>JUnit 5 (Java)</h3> | |
| <pre>@Test | |
| void should_not_send_any_data_if_there_is_no_token() { | |
| // given | |
| String dashboardId = "1"; | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage(); | |
| sqsMessage.setBody(dashboardId); | |
| SQSEvent sqsEvent = new SQSEvent(); | |
| sqsEvent.setRecords(List.of(sqsMessage)); | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder() | |
| .organizationUid("organization_1") | |
| .workspaceUid("workspace_1") | |
| .token(null) | |
| .build()) | |
| .build(); | |
| when(googleAnalyticsDashboardService.getDashboard(1L)).thenReturn(dashboard); | |
| // when | |
| service.handleSynchronizations(sqsEvent); | |
| // then | |
| verify(googleAnalyticsDashboardService).getDashboard(1L); | |
| verifyNoMoreInteractions(googleAnalyticsDashboardService); | |
| verifyNoInteractions(subscriptionContextService, organizationClient, managerClient, roiClient); | |
| }</pre> | |
| </div> | |
| </div> | |
| <div class="summary"> | |
| <p><strong>Changes:</strong> Tests early exit when token is null. Dashboard exists but has no token, so subscription check is skipped entirely. Converted dashboardId.toLong() to literal 1L.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Test 7: should send data if there is token and owner --> | |
| <div class="section"> | |
| <div class="section-header" onclick="toggleSection(this)"> | |
| <h2>🧪 should send data if there is token and owner</h2> | |
| <span class="toggle">▼</span> | |
| </div> | |
| <div class="section-content"> | |
| <div class="diff-container"> | |
| <div class="diff-panel left"> | |
| <h3>Spock (Groovy)</h3> | |
| <pre>void 'should send data if there is token and owner'() { | |
| given: | |
| String dashboardId = '1' | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage() | |
| sqsMessage.body = dashboardId | |
| SQSEvent sqsEvent = new SQSEvent(records: [sqsMessage]) | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder().organizationUid('organization_1').token('token').workspaceUid('workspace_1').build()) | |
| .build() | |
| when: | |
| service.handleSynchronizations(sqsEvent) | |
| then: | |
| 1 * googleAnalyticsDashboardService.getDashboard(dashboardId.toLong()) >> dashboard | |
| 1 * subscriptionContextService.getSubscriptionContextForRoi(1L) >> Optional.of(SubscriptionContext.builder().type(SubscriptionType.PAID).build()) | |
| 1 * organizationClient.getOrganization(1L) >> new OrganizationSummary( | |
| organizationManagers: [new OrganizationManagerSummary(role: OrganizationRole.owner, managerId: 1)] | |
| ) | |
| 1 * managerClient.getManager(1) >> new ManagerSummary(identityId: 'identityId') | |
| 1 * roiClient.buildRoi(_) | |
| 0 * _ | |
| }</pre> | |
| </div> | |
| <div class="diff-panel right"> | |
| <h3>JUnit 5 (Java)</h3> | |
| <pre>@Test | |
| void should_send_data_if_there_is_token_and_owner() { | |
| // given | |
| String dashboardId = "1"; | |
| SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage(); | |
| sqsMessage.setBody(dashboardId); | |
| SQSEvent sqsEvent = new SQSEvent(); | |
| sqsEvent.setRecords(List.of(sqsMessage)); | |
| GoogleAnalyticsDashboardEntity dashboard = GoogleAnalyticsDashboardEntity.builder() | |
| .googleAnalyticsAccount(GoogleAnalyticsAccountEntity.builder() | |
| .organizationUid("organization_1") | |
| .token("token") | |
| .workspaceUid("workspace_1") | |
| .build()) | |
| .build(); | |
| OrganizationManagerSummary ownerManager = new OrganizationManagerSummary(); | |
| ownerManager.setRole(OrganizationRole.owner); | |
| ownerManager.setManagerId(1L); | |
| OrganizationSummary organizationSummary = new OrganizationSummary(); | |
| organizationSummary.setOrganizationManagers(List.of(ownerManager)); | |
| ManagerSummary managerSummary = new ManagerSummary(); | |
| managerSummary.setIdentityId("identityId"); | |
| when(googleAnalyticsDashboardService.getDashboard(1L)).thenReturn(dashboard); | |
| when(subscriptionContextService.getSubscriptionContextForRoi(1L)) | |
| .thenReturn(Optional.of(SubscriptionContext.builder().type(SubscriptionType.PAID).build())); | |
| when(organizationClient.getOrganization(1L)).thenReturn(organizationSummary); | |
| when(managerClient.getManager(1L)).thenReturn(managerSummary); | |
| // when | |
| service.handleSynchronizations(sqsEvent); | |
| // then | |
| verify(googleAnalyticsDashboardService).getDashboard(1L); | |
| verify(subscriptionContextService).getSubscriptionContextForRoi(1L); | |
| verify(organizationClient).getOrganization(1L); | |
| verify(managerClient).getManager(1L); | |
| verify(roiClient).buildRoi(any()); | |
| verifyNoMoreInteractions(googleAnalyticsDashboardService, subscriptionContextService, organizationClient, managerClient, roiClient); | |
| }</pre> | |
| </div> | |
| </div> | |
| <div class="summary"> | |
| <p><strong>Changes:</strong> Happy path test - full flow through to roiClient.buildRoi(). Groovy constructor with named params converted to Java setters. Spock's _ wildcard matcher converted to Mockito's any(). All mocks verified with verifyNoMoreInteractions().</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| function toggleSection(header) { | |
| const content = header.nextElementSibling; | |
| const toggle = header.querySelector('.toggle'); | |
| content.classList.toggle('collapsed'); | |
| toggle.textContent = content.classList.contains('collapsed') ? '▶' : '▼'; | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment