diff --git a/openespi-common/src/main/resources/META-INF/resources/css/gba-theme.css b/openespi-common/src/main/resources/META-INF/resources/css/gba-theme.css index 7ce8927d..ab86109e 100644 --- a/openespi-common/src/main/resources/META-INF/resources/css/gba-theme.css +++ b/openespi-common/src/main/resources/META-INF/resources/css/gba-theme.css @@ -58,12 +58,6 @@ --bs-border-radius-sm: 0; --bs-border-radius-lg: 0; --bs-focus-ring-color: rgba(249, 151, 66, 0.45); - - /* Data Custodian application.css tokens, so its existing rules adopt the GBA palette */ - --gb-primary: var(--gba-green-900); - --gb-secondary: var(--gba-green-700); - --gb-accent: var(--gba-green-700); - --gb-warning: var(--gba-orange); } body[data-gba-app="thirdparty"] { @@ -79,7 +73,9 @@ body { min-height: 100vh; } -main { +/* The page content grows so the footer sits at the bottom of short pages. */ +main, +body > .container { flex: 1 0 auto; } @@ -172,11 +168,6 @@ h1, h2, h3, h4, h5, h6, --bs-btn-border-width: 2px; --bs-btn-font-weight: 500; --bs-btn-font-family: var(--gba-font); - border-radius: 0; /* the Data Custodian application.css sets a literal 0.5rem */ -} - -.btn:hover { - transform: none; } .btn-primary { @@ -236,12 +227,13 @@ h1, h2, h3, h4, h5, h6, .gba-hero, .hero-unit { + /* !important beats Bootstrap's !important utilities (bg-*, text-*, rounded) that hero markup + still carries; hero backgrounds are always dark, so the text must stay white. */ background: var(--gba-hero-bg) !important; - /* !important: hero backgrounds are always dark, so override text-dark and similar utilities */ color: #fff !important; border-left: 6px solid var(--gba-orange); border-radius: 0 !important; - box-shadow: none !important; + margin-bottom: 2rem; padding: 3rem; } @@ -266,20 +258,13 @@ h1, h2, h3, h4, h5, h6, .card { --bs-card-bg: #FFFFFF; /* white cards on the grey page, as on greenbuttonalliance.org */ - border: 1px solid #DDDDDD; - border-radius: 0 !important; - box-shadow: none; + --bs-card-border-color: #DDDDDD; } .card:hover { - transform: none; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } -.card-header { - border-radius: 0 !important; -} - .gba-card-header { background-color: var(--gba-green-900); color: #fff; @@ -300,8 +285,42 @@ h1, h2, h3, h4, h5, h6, color: var(--gba-green-700); } +/* Large status icon on error pages (keeps its semantic text-warning / text-danger colour) */ +.gba-status-icon { + font-size: 5rem; + line-height: 1; +} + +/* Dashboard statistic tile: white card with a green left rule and a large figure */ +.gba-stat { + border-left: 6px solid var(--gba-green-900); +} + +.gba-stat .gba-stat-value { + color: var(--gba-green-900); + font-size: 2.25rem; + font-weight: 700; + line-height: 1.1; +} + +.gba-stat .gba-stat-label { + color: var(--gba-text); + font-weight: 500; +} + /* ---- Forms ----------------------------------------------------------------------------- */ +/* Bootstrap fills inputs and tables with --bs-body-bg, which the theme sets to the grey page + colour; keep them white, as on greenbuttonalliance.org. */ +.form-control, +.form-select { + background-color: #FFFFFF; +} + +.table { + --bs-table-bg: #FFFFFF; +} + .form-control:focus, .form-select:focus { border-color: var(--gba-orange); @@ -318,8 +337,10 @@ h1, h2, h3, h4, h5, h6, padding: 2rem 0 1.5rem; } -/* Data Custodian templates place the footer inside the page .container; let it span the full - viewport anyway (body clips the scrollbar-width overshoot of 100vw). */ +/* Pages should place the footer after the page container, as a child of . The two legacy + Data Custodian customer pages (customer/home, customer/usagepoints/index; #245) still place it + inside the .container, so let it span the full viewport there too (body clips the + scrollbar-width overshoot of 100vw). Remove once #245 rebuilds those pages. */ .container > .gba-footer { margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw); diff --git a/openespi-datacustodian/src/main/resources/static/css/application.css b/openespi-datacustodian/src/main/resources/static/css/application.css index fe463f47..a742f1f6 100644 --- a/openespi-datacustodian/src/main/resources/static/css/application.css +++ b/openespi-datacustodian/src/main/resources/static/css/application.css @@ -1,278 +1,44 @@ /* * Green Button Data Custodian Application Styles * Copyright (c) 2018-2026 Green Button Alliance, Inc. + * + * Colours, typography, header, footer, buttons, cards and heroes come from the shared Green Button + * Alliance theme (/css/gba-theme.css, shipped in OpenESPI-Common), which fragments/layout loads + * after this file. Keep only Data Custodian-specific rules here and use the theme's --gba-* tokens; + * do not redefine the palette, corner radii or component chrome. */ -/* Custom Green Button Color Palette */ -:root { - --gb-primary: #2E7D32; /* Green Button Green */ - --gb-secondary: #388E3C; /* Lighter Green */ - --gb-accent: #4CAF50; /* Success Green */ - --gb-warning: #FF9800; /* Orange */ - --gb-danger: #F44336; /* Red */ - --gb-info: #2196F3; /* Blue */ - --gb-light: #F5F5F5; /* Light Gray */ - --gb-dark: #212121; /* Dark Gray */ -} - -/* Override Bootstrap Primary Colors */ -.bg-primary { - background-color: var(--gb-primary) !important; -} - -.btn-primary { - background-color: var(--gb-primary); - border-color: var(--gb-primary); -} - -.btn-primary:hover { - background-color: var(--gb-secondary); - border-color: var(--gb-secondary); -} - -.navbar-brand { - font-weight: 600; - font-size: 1.5rem; -} - -/* Hero Unit Styling */ -.hero-unit { - margin-bottom: 2rem; - background: linear-gradient(135deg, var(--gb-primary) 0%, var(--gb-secondary) 100%); - border-radius: 0.5rem; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); -} - -.hero-unit h1, .hero-unit h2 { - margin-bottom: 1rem; - font-weight: 600; -} - -.hero-unit .lead { - font-size: 1.25rem; - font-weight: 300; -} - -/* Card Enhancements */ -.card { - border: none; - border-radius: 0.75rem; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - transition: all 0.3s ease; -} - -.card:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); -} - -.card-header { - border-radius: 0.75rem 0.75rem 0 0 !important; - font-weight: 600; -} - -/* Navigation Enhancements */ -.navbar { - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} - -.nav-link { - font-weight: 500; - transition: all 0.3s ease; -} - -.nav-link:hover { - transform: translateY(-1px); -} - -/* Form Styling */ -.form-control { - border-radius: 0.5rem; - border: 2px solid #e0e0e0; - transition: all 0.3s ease; -} - -.form-control:focus { - border-color: var(--gb-primary); - box-shadow: 0 0 0 0.2rem rgba(46, 125, 50, 0.25); -} - -.btn { - border-radius: 0.5rem; - font-weight: 500; - transition: all 0.3s ease; -} - -.btn:hover { - transform: translateY(-1px); -} - -/* Dashboard Statistics Cards */ -.stats-card { - background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); - border-left: 4px solid var(--gb-primary); -} - -/* Alert Styling */ -.alert { - border-radius: 0.5rem; - border: none; - font-weight: 500; -} - -.alert-success { - background-color: rgba(76, 175, 80, 0.1); - color: var(--gb-primary); -} - -.alert-danger { - background-color: rgba(244, 67, 54, 0.1); - color: var(--gb-danger); -} - -.alert-warning { - background-color: rgba(255, 152, 0, 0.1); - color: var(--gb-warning); -} - -.alert-info { - background-color: rgba(33, 150, 243, 0.1); - color: var(--gb-info); -} - -/* Footer Styling */ -footer { - background-color: var(--gb-light); - border-top: 1px solid #e0e0e0; -} - -footer a { - color: var(--gb-primary); - text-decoration: none; - font-weight: 500; -} - -footer a:hover { - color: var(--gb-secondary); - text-decoration: underline; -} - -/* Error Page Styling */ -.error-page { - min-height: 60vh; - display: flex; - align-items: center; - justify-content: center; -} - -/* Loading Spinner */ -.spinner-border-green { - color: var(--gb-primary); -} - -/* Data Table Enhancements */ -.table { - border-radius: 0.5rem; - overflow: hidden; -} - +/* Data tables: GBA dark-green header row */ .table thead th { - background-color: var(--gb-primary); - color: white; + background-color: var(--gba-green-900); + color: #FFFFFF; border: none; - font-weight: 600; -} - -.table tbody tr:hover { - background-color: rgba(46, 125, 50, 0.05); -} - -/* Badge Styling */ -.badge { - border-radius: 0.5rem; font-weight: 500; } -.badge-success { - background-color: var(--gb-accent); -} - -.badge-primary { - background-color: var(--gb-primary); -} - -/* Progress Bar */ -.progress { - border-radius: 0.5rem; - height: 0.75rem; -} - -.progress-bar { - background-color: var(--gb-primary); -} - -/* Responsive Adjustments */ -@media (max-width: 768px) { - .hero-unit { - padding: 2rem 1.5rem; - text-align: center; - } - - .hero-unit h1 { - font-size: 2rem; - } - - .hero-unit h2 { - font-size: 1.5rem; - } - - .navbar-brand { - font-size: 1.25rem; - } -} - -/* Animation Classes */ -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px); } - to { opacity: 1; transform: translateY(0); } -} - -.fade-in { - animation: fadeIn 0.6s ease-out; -} - -/* Utility Classes */ -.text-green { - color: var(--gb-primary) !important; -} - -.bg-green { - background-color: var(--gb-primary) !important; -} - -.border-green { - border-color: var(--gb-primary) !important; +.table tbody tr:hover { + background-color: var(--gba-green-050); } -/* Focus Visible for Accessibility */ +/* Focus visible for accessibility (orange, as the theme's focus ring) */ .btn:focus-visible, .nav-link:focus-visible, .form-control:focus-visible { - outline: 2px solid var(--gb-primary); + outline: 2px solid var(--gba-orange-700); outline-offset: 2px; } -/* Print Styles */ +/* Print: content only */ @media print { .navbar, .btn, footer { display: none !important; } - + .container { max-width: none !important; margin: 0; padding: 0; } -} \ No newline at end of file +} diff --git a/openespi-datacustodian/src/main/resources/templates/about.html b/openespi-datacustodian/src/main/resources/templates/about.html index 7f95c011..d2449459 100644 --- a/openespi-datacustodian/src/main/resources/templates/about.html +++ b/openespi-datacustodian/src/main/resources/templates/about.html @@ -27,8 +27,8 @@

About

-
- + + diff --git a/openespi-datacustodian/src/main/resources/templates/authorize-screen.html b/openespi-datacustodian/src/main/resources/templates/authorize-screen.html index 0aac41a2..715c9247 100644 --- a/openespi-datacustodian/src/main/resources/templates/authorize-screen.html +++ b/openespi-datacustodian/src/main/resources/templates/authorize-screen.html @@ -103,10 +103,10 @@
Personal information
- - + + diff --git a/openespi-datacustodian/src/main/resources/templates/custodian/home.html b/openespi-datacustodian/src/main/resources/templates/custodian/home.html index 9a967da0..7d11e137 100644 --- a/openespi-datacustodian/src/main/resources/templates/custodian/home.html +++ b/openespi-datacustodian/src/main/resources/templates/custodian/home.html @@ -9,7 +9,7 @@
-
+

Data Custodian Dashboard

Manage retail customers, monitor OAuth authorizations, and administer Green Button data access. @@ -21,7 +21,7 @@

Data Custodian Dashboard

- +
Retail Customers

Manage customer accounts and their usage points.

Manage Customers @@ -32,10 +32,10 @@
Retail Customers
- +
OAuth Tokens

Monitor and manage OAuth access tokens.

- View Tokens + View Tokens
@@ -43,10 +43,10 @@
OAuth Tokens
- +
Data Upload

Upload customer usage data in bulk.

- Upload Data + Upload Data
@@ -54,10 +54,10 @@
Data Upload
- +
Notify Third Party

Send an ESPI BatchList of resource URLs to a third party.

- Notify + Notify
@@ -65,10 +65,10 @@
Notify Third Party
- +
System Settings

Configure system-wide settings and preferences.

- Settings + Settings
@@ -77,70 +77,60 @@
System Settings
-

System Overview

+

System Overview

- +
-
-
-
-
-

0

-

Total Customers

-
- +
+
+
+
0
+
Total Customers
+
-
-
-
-
-

0

-

Active Tokens

-
- +
+
+
+
0
+
Active Tokens
+
-
-
-
-
-

0

-

Usage Points

-
- +
+
+
+
0
+
Usage Points
+
-
-
-
-
-

0

-

Today's API Requests

-
- +
+
+
+
0
+
Today's API Requests
+
- -
- -
+
+ \ No newline at end of file diff --git a/openespi-datacustodian/src/main/resources/templates/custodian/notifications.html b/openespi-datacustodian/src/main/resources/templates/custodian/notifications.html index a4eb9648..34318779 100644 --- a/openespi-datacustodian/src/main/resources/templates/custodian/notifications.html +++ b/openespi-datacustodian/src/main/resources/templates/custodian/notifications.html @@ -109,8 +109,8 @@
BatchList resources — select which to send
-
-
+ +
diff --git a/openespi-datacustodian/src/main/resources/templates/custodian/oauth/tokens.html b/openespi-datacustodian/src/main/resources/templates/custodian/oauth/tokens.html index 9f81eaea..ab3381ca 100644 --- a/openespi-datacustodian/src/main/resources/templates/custodian/oauth/tokens.html +++ b/openespi-datacustodian/src/main/resources/templates/custodian/oauth/tokens.html @@ -47,8 +47,8 @@

OAuth Token Management

-
-
+ +
diff --git a/openespi-datacustodian/src/main/resources/templates/custodian/password.html b/openespi-datacustodian/src/main/resources/templates/custodian/password.html index b73e1df2..f2dd7e76 100644 --- a/openespi-datacustodian/src/main/resources/templates/custodian/password.html +++ b/openespi-datacustodian/src/main/resources/templates/custodian/password.html @@ -36,8 +36,8 @@

Change Password

-
-
+ +
diff --git a/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/form.html b/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/form.html index 8171c654..3e0122d2 100644 --- a/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/form.html +++ b/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/form.html @@ -64,8 +64,8 @@

-

+ +
diff --git a/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/index.html b/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/index.html index 9dd6ae3c..86957392 100644 --- a/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/index.html +++ b/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/index.html @@ -53,7 +53,8 @@

Retail Customers

- + +
@@ -66,8 +67,8 @@

Retail Customers

-
-
+ +
diff --git a/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/show.html b/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/show.html index 51503f57..5679d44f 100644 --- a/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/show.html +++ b/openespi-datacustodian/src/main/resources/templates/custodian/retailcustomers/show.html @@ -64,8 +64,8 @@

Customer Name

write was not part of the resource-server migration).
-
-
+ + diff --git a/openespi-datacustodian/src/main/resources/templates/custodian/settings.html b/openespi-datacustodian/src/main/resources/templates/custodian/settings.html index 5ca539e7..903ba616 100644 --- a/openespi-datacustodian/src/main/resources/templates/custodian/settings.html +++ b/openespi-datacustodian/src/main/resources/templates/custodian/settings.html @@ -39,8 +39,8 @@

System Settings

-
- + + diff --git a/openespi-datacustodian/src/main/resources/templates/custodian/upload.html b/openespi-datacustodian/src/main/resources/templates/custodian/upload.html index 2909e622..385b9d8b 100644 --- a/openespi-datacustodian/src/main/resources/templates/custodian/upload.html +++ b/openespi-datacustodian/src/main/resources/templates/custodian/upload.html @@ -30,8 +30,8 @@

Data Upload

-
- + + diff --git a/openespi-datacustodian/src/main/resources/templates/customer/authorizations.html b/openespi-datacustodian/src/main/resources/templates/customer/authorizations.html index d21660da..c3b8f4a0 100644 --- a/openespi-datacustodian/src/main/resources/templates/customer/authorizations.html +++ b/openespi-datacustodian/src/main/resources/templates/customer/authorizations.html @@ -57,8 +57,8 @@

My Authorizations

-
- + + diff --git a/openespi-datacustodian/src/main/resources/templates/customer/password.html b/openespi-datacustodian/src/main/resources/templates/customer/password.html index e7c4878e..3e0e5719 100644 --- a/openespi-datacustodian/src/main/resources/templates/customer/password.html +++ b/openespi-datacustodian/src/main/resources/templates/customer/password.html @@ -36,8 +36,8 @@

Change Password

-
- + + diff --git a/openespi-datacustodian/src/main/resources/templates/error.html b/openespi-datacustodian/src/main/resources/templates/error.html index c036dacd..c2340dd5 100644 --- a/openespi-datacustodian/src/main/resources/templates/error.html +++ b/openespi-datacustodian/src/main/resources/templates/error.html @@ -11,7 +11,7 @@
- +

Error

Something went wrong

@@ -35,13 +35,13 @@
Error Details:
Go Home - - + +
- - + + \ No newline at end of file diff --git a/openespi-datacustodian/src/main/resources/templates/error/400.html b/openespi-datacustodian/src/main/resources/templates/error/400.html index 9da95f40..0bbbec93 100644 --- a/openespi-datacustodian/src/main/resources/templates/error/400.html +++ b/openespi-datacustodian/src/main/resources/templates/error/400.html @@ -11,7 +11,7 @@
- +

400

Bad Request

@@ -24,8 +24,8 @@

Bad Request

- - + + \ No newline at end of file diff --git a/openespi-datacustodian/src/main/resources/templates/error/403.html b/openespi-datacustodian/src/main/resources/templates/error/403.html index 42ecf2e9..501c6aa1 100644 --- a/openespi-datacustodian/src/main/resources/templates/error/403.html +++ b/openespi-datacustodian/src/main/resources/templates/error/403.html @@ -11,7 +11,7 @@
- +

403

Access Forbidden

@@ -25,8 +25,8 @@

Access Forbidden

- - + + \ No newline at end of file diff --git a/openespi-datacustodian/src/main/resources/templates/home.html b/openespi-datacustodian/src/main/resources/templates/home.html index ee25b056..a143ca01 100644 --- a/openespi-datacustodian/src/main/resources/templates/home.html +++ b/openespi-datacustodian/src/main/resources/templates/home.html @@ -9,7 +9,7 @@
-
+

Welcome to the Green Button Alliance Data Custodian

The Green Button Alliance has, with the support of the open source community, prepared a reference Green Button @@ -66,9 +66,9 @@

Welcome to the

+
+ \ No newline at end of file diff --git a/openespi-datacustodian/src/main/resources/templates/login.html b/openespi-datacustodian/src/main/resources/templates/login.html index 7f6d3907..532b5ef9 100644 --- a/openespi-datacustodian/src/main/resources/templates/login.html +++ b/openespi-datacustodian/src/main/resources/templates/login.html @@ -56,30 +56,30 @@

Login to Data Custodian

- +
Secure Access

Your data is protected with industry-standard security measures.

- +
Green Button Standard

Compliant with the North American Energy Standards Board (NAESB) specifications.

- +
Customer Control

You maintain full control over who can access your energy data.

- -
+
+ diff --git a/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/GbaThemeResourcesTest.java b/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/GbaThemeResourcesTest.java new file mode 100644 index 00000000..2737f3f7 --- /dev/null +++ b/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/GbaThemeResourcesTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2026 Green Button Alliance, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.greenbuttonalliance.espi.datacustodian.web; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * The shared Green Button Alliance theme, logo and favicon ship in the OpenESPI-Common jar + * (META-INF/resources) and must be served anonymously, and the portal layout must reference them. + * Guards both the DC resource handlers (@EnableWebMvc disables Boot's default static mappings) and + * the root-pom resource packaging that once dropped every binary file from the jars. + */ +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +class GbaThemeResourcesTest { + + @Autowired + private MockMvc mockMvc; + + @ParameterizedTest + @CsvSource({ + "/css/gba-theme.css, text/css", + "/images/gba-logo.png, image/png", + "/images/gba-favicon.png, image/png", + "/images/favicon.png, image/png"}) + void shared_assets_are_served_anonymously(String path, String contentType) throws Exception { + byte[] body = mockMvc.perform(get(path)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(contentType)) + .andReturn().getResponse().getContentAsByteArray(); + + assertThat(body).isNotEmpty(); + } + + @Test + void logo_is_packaged_unfiltered() throws Exception { + byte[] logo = mockMvc.perform(get("/images/gba-logo.png")) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsByteArray(); + + // PNG signature: resource filtering would have corrupted these bytes + assertThat(logo).startsWith(0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A); + } + + @Test + void home_page_uses_the_gba_layout() throws Exception { + String html = mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + assertThat(html) + .contains("/css/gba-theme.css", "/images/gba-logo.png", "/images/gba-favicon.png") + .contains("gba-navbar", "gba-footer") + .contains("integrity=\"sha384-"); + } +} diff --git a/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/web/GbaLayoutPagesTest.java b/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/web/GbaLayoutPagesTest.java new file mode 100644 index 00000000..372847b5 --- /dev/null +++ b/openespi-thirdparty/src/test/java/org/greenbuttonalliance/espi/thirdparty/web/GbaLayoutPagesTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2026 Green Button Alliance, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.greenbuttonalliance.espi.thirdparty.web; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Third Party pages render through fragments/layout (shared GBA theme, logo, context-relative + * links) and the public pages linked from the navbar are reachable without logging in. + */ +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +class GbaLayoutPagesTest { + + @Autowired + private MockMvc mockMvc; + + @ParameterizedTest + @ValueSource(strings = {"/", "/login", "/TermsOfService", "/UsagePolicy"}) + void public_pages_render_anonymously_with_the_gba_layout(String path) throws Exception { + String html = mockMvc.perform(get(path)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + assertThat(html) + .contains("/css/gba-theme.css", "/images/gba-logo.png", "/images/gba-favicon.png") + .contains("gba-navbar", "gba-footer", "data-gba-app=\"thirdparty\"") + .contains("integrity=\"sha384-") + // no root-relative internal links (#235): the layout uses th:href="@{...}" + .doesNotContain("href=\"/about\""); + } + + @ParameterizedTest + @CsvSource({ + "/css/gba-theme.css, text/css", + "/images/gba-logo.png, image/png", + "/images/gba-favicon.png, image/png"}) + void shared_assets_are_served_anonymously(String path, String contentType) throws Exception { + mockMvc.perform(get(path)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(contentType)); + } +}