From 99081ff2364e54c90c08b489e5766bcc0a013d1c Mon Sep 17 00:00:00 2001 From: Aaron Brethorst Date: Thu, 18 Sep 2025 19:44:35 -0700 Subject: [PATCH 1/5] Adds support for multiple agencies to docker image --- README.md | 4 +- oba/bootstrap.sh | 27 ++++++++- ...ata-federation-webapp-data-sources.xml.hbs | 12 +++- oba/config/template_renderer/main_test.go | 55 +++++++++++++++++++ 4 files changed, 95 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7e38f41..e7d0d35 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,9 @@ You can find the latest published Docker images on Docker Hub: * `TRIP_UPDATES_URL` - Trip Updates URL for GTFS-RT. * `VEHICLE_POSITIONS_URL` - Vehicle Positions URL for GTFS-RT. * `REFRESH_INTERVAL` - Refresh interval in seconds. Usually 10-30. - * `AGENCY_ID` - Your GTFS-RT agency ID. Ostensibly the same as your GTFS agency ID. + * Specify one or the other: + * `AGENCY_ID_LIST` - Your GTFS-RT agency IDs. These should match the IDs in your agency.txt file. Format: abxoxo + * `AGENCY_ID` - Optional: Your GTFS-RT agency ID. Ostensibly the same as your GTFS agency ID. * Authentication (Optional) * Example: Specifying `FEED_API_KEY` = `X-API-KEY` and `FEED_API_VALUE` = `12345` will result in `X-API-KEY: 12345` being passed on every call to your GTFS-RT URLs. * `FEED_API_KEY` - If your GTFS-RT API requires you to pass an authentication header, you can represent the key portion of it by specifying this value. diff --git a/oba/bootstrap.sh b/oba/bootstrap.sh index f7c9cbc..545ed66 100755 --- a/oba/bootstrap.sh +++ b/oba/bootstrap.sh @@ -46,8 +46,33 @@ else HAS_API_KEY="" fi +# Handle AGENCY_ID_LIST properly - if it's a JSON array, use it directly, otherwise treat as empty +if [ -n "$AGENCY_ID_LIST" ]; then + # Remove the outer quotes if they exist and use the array directly + AGENCY_ID_LIST_JSON="$AGENCY_ID_LIST" +else + AGENCY_ID_LIST_JSON="[]" +fi + +# Build the JSON string with proper handling of AGENCY_ID_LIST +JSON_CONFIG=$(cat < {{/if}} - + {{#if AGENCY_ID_LIST}} + + + {{#each AGENCY_ID_LIST}} + {{ this }} + {{/each}} + + + {{else}} + + {{/if}} {{#if HAS_API_KEY}} diff --git a/oba/config/template_renderer/main_test.go b/oba/config/template_renderer/main_test.go index a0c70e3..7d3f400 100644 --- a/oba/config/template_renderer/main_test.go +++ b/oba/config/template_renderer/main_test.go @@ -61,6 +61,61 @@ func TestRenderTemplate(t *testing.T) { } } +func TestRenderTemplateWithArray(t *testing.T) { + // Create a temporary template file + tempFile, err := os.CreateTemp("", "test-template-*.hbs") + if err != nil { + t.Fatalf("Failed to create temp file: %v", err) + } + defer os.Remove(tempFile.Name()) // clean up + + // Write test template content + templateContent := "Hello, {{name}}! Your favorite colors are {{#each colors}}{{this}} {{else}}unknown{{/each}}." + if _, err := tempFile.Write([]byte(templateContent)); err != nil { + t.Fatalf("Failed to write to temp file: %v", err) + } + if err := tempFile.Close(); err != nil { + t.Fatalf("Failed to close temp file: %v", err) + } + + // Set up test cases + testCases := []struct { + name string + jsonData string + expected string + }{ + { + name: "Basic rendering", + jsonData: `{"name": "Alice", "colors": ["blue", "green"]}`, + expected: "Hello, Alice! Your favorite colors are blue green .", + }, + { + name: "only one item rendering", + jsonData: `{"name": "Alice", "colors": ["green"]}`, + expected: "Hello, Alice! Your favorite colors are green .", + }, + { + name: "only one item rendering", + jsonData: `{"name": "Alice", "colors": []}`, + expected: "Hello, Alice! Your favorite colors are unknown.", + }, + } + + // Run test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := renderTemplate(tempFile.Name(), tc.jsonData) + if err != nil { + t.Fatalf("renderTemplate returned an error: %v", err) + } + + if !strings.Contains(result, tc.expected) { + t.Errorf("Expected output to contain %q, but got %q", tc.expected, result) + } + }) + } +} + func TestRenderTemplateErrors(t *testing.T) { // Test with non-existent file _, err := renderTemplate("non-existent-file.hbs", "{}") From a0fa4a86d427778d2798269e24d7ead27b391934 Mon Sep 17 00:00:00 2001 From: Aaron Brethorst Date: Thu, 18 Sep 2025 20:13:45 -0700 Subject: [PATCH 2/5] Improves validate script --- bin/validate.sh | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/bin/validate.sh b/bin/validate.sh index 22b36c4..8f9d99b 100755 --- a/bin/validate.sh +++ b/bin/validate.sh @@ -1,6 +1,9 @@ #!/bin/bash -output=$(curl -s "http://localhost:8080/api/where/current-time.json?key=test" | jq '.data.entry.time') +# Timeout for curl requests in seconds +CURL_TIMEOUT=120 + +output=$(curl -s --max-time $CURL_TIMEOUT "http://localhost:8080/api/where/current-time.json?key=test" | jq '.data.entry.time') if [[ ! -z "$output" && "$output" =~ ^[0-9]+$ ]]; then echo "current-time.json endpoint works." @@ -10,7 +13,20 @@ else fi # Get the first agency from agencies-with-coverage -agency_response=$(curl -s "http://localhost:8080/api/where/agencies-with-coverage.json?key=test") +agency_response=$(curl -s --max-time $CURL_TIMEOUT "http://localhost:8080/api/where/agencies-with-coverage.json?key=test") + +# Debug: Show raw response if empty or small +if [[ -z "$agency_response" || ${#agency_response} -lt 100 ]]; then + echo "Debug: Raw agency response: '$agency_response'" +fi + +# Check if response is valid JSON +if ! echo "$agency_response" | jq empty 2>/dev/null; then + echo "Error: Invalid JSON response from agencies-with-coverage endpoint" + echo "Response: $agency_response" + exit 1 +fi + agency_count=$(echo "$agency_response" | jq '.data.list | length') if [[ "$agency_count" -gt 0 ]]; then @@ -23,7 +39,7 @@ else fi # Get routes for the agency -routes_response=$(curl -s "http://localhost:8080/api/where/routes-for-agency/${AGENCY_ID}.json?key=test") +routes_response=$(curl -s --max-time $CURL_TIMEOUT "http://localhost:8080/api/where/routes-for-agency/${AGENCY_ID}.json?key=test") route_count=$(echo "$routes_response" | jq '.data.list | length') if [[ "$route_count" -gt 0 ]]; then echo "routes-for-agency/${AGENCY_ID}.json endpoint works (found $route_count routes)." @@ -35,7 +51,7 @@ else fi # Get stops for the route -stops_response=$(curl -s "http://localhost:8080/api/where/stops-for-route/${ROUTE_ID}.json?key=test") +stops_response=$(curl -s --max-time $CURL_TIMEOUT "http://localhost:8080/api/where/stops-for-route/${ROUTE_ID}.json?key=test") route_id_check=$(echo "$stops_response" | jq -r '.data.entry.routeId') if [[ ! -z "$route_id_check" && "$route_id_check" == "$ROUTE_ID" ]]; then echo "stops-for-route/${ROUTE_ID}.json endpoint works." @@ -47,7 +63,7 @@ else fi # Get stop details -stop_response=$(curl -s "http://localhost:8080/api/where/stop/${STOP_ID}.json?key=test") +stop_response=$(curl -s --max-time $CURL_TIMEOUT "http://localhost:8080/api/where/stop/${STOP_ID}.json?key=test") stop_id_check=$(echo "$stop_response" | jq -r '.data.entry.id') if [[ ! -z "$stop_id_check" && "$stop_id_check" == "$STOP_ID" ]]; then echo "stop/${STOP_ID}.json endpoint works." @@ -62,7 +78,7 @@ fi # Test stops-for-location using coordinates from the stop LOCATION_URL="http://localhost:8080/api/where/stops-for-location.json?lat=${STOP_LAT}&lon=${STOP_LON}&key=test" -location_response=$(curl -s "$LOCATION_URL") +location_response=$(curl -s --max-time $CURL_TIMEOUT "$LOCATION_URL") out_of_range=$(echo "$location_response" | jq '.data.outOfRange') stops_found=$(echo "$location_response" | jq '.data.list | length') if [[ ! -z "$out_of_range" && "$out_of_range" == "false" && "$stops_found" -gt 0 ]]; then @@ -75,7 +91,7 @@ else fi # Test arrivals-and-departures-for-stop endpoint -arrivals_response=$(curl -s "http://localhost:8080/api/where/arrivals-and-departures-for-stop/${STOP_ID}.json?key=test") +arrivals_response=$(curl -s --max-time $CURL_TIMEOUT "http://localhost:8080/api/where/arrivals-and-departures-for-stop/${STOP_ID}.json?key=test") arrivals_stop_id=$(echo "$arrivals_response" | jq -r '.data.entry.stopId') arrivals_count=$(echo "$arrivals_response" | jq '.data.entry.arrivalsAndDepartures | length // 0') From 0de75f6175b11c91e2dff38c8c11516b0758e71c Mon Sep 17 00:00:00 2001 From: Aaron Brethorst Date: Thu, 18 Sep 2025 21:01:42 -0700 Subject: [PATCH 3/5] Try to fix errors in the CI standalone scenario --- ...usaway-transit-data-federation-webapp-data-sources.xml.hbs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oba/config/onebusaway-transit-data-federation-webapp-data-sources.xml.hbs b/oba/config/onebusaway-transit-data-federation-webapp-data-sources.xml.hbs index 17aacba..ce9dadf 100644 --- a/oba/config/onebusaway-transit-data-federation-webapp-data-sources.xml.hbs +++ b/oba/config/onebusaway-transit-data-federation-webapp-data-sources.xml.hbs @@ -64,7 +64,7 @@ {{/if}} - {{#if AGENCY_ID_LIST}} + {{#if AGENCY_ID_LIST.length}} {{#each AGENCY_ID_LIST}} @@ -72,7 +72,7 @@ {{/each}} - {{else}} + {{else if AGENCY_ID}} {{/if}} From 89b08fd1ae472720f508c18a649b9dab25f45e8b Mon Sep 17 00:00:00 2001 From: Aaron Brethorst Date: Thu, 18 Sep 2025 21:09:03 -0700 Subject: [PATCH 4/5] Explicitly set AGENCY_ID in standalone docker compose file --- docker-compose.standalone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.standalone.yml b/docker-compose.standalone.yml index 7de2b9b..6b459d3 100644 --- a/docker-compose.standalone.yml +++ b/docker-compose.standalone.yml @@ -43,6 +43,7 @@ services: - JDBC_PASSWORD=oba_password - TEST_API_KEY=test # For test only, remove in production - TZ=America/Los_Angeles + - AGENCY_ID=unitrans - GTFS_URL=https://unitrans.ucdavis.edu/media/gtfs/Unitrans_GTFS.zip - GTFS_TIDY_ARGS=v From d32d070bdb8e0f1cc957607a66da6071b935373f Mon Sep 17 00:00:00 2001 From: Aaron Brethorst Date: Thu, 18 Sep 2025 21:41:57 -0700 Subject: [PATCH 5/5] Fiddle with gtfstidy args to get the bundle to build correctly in standalone --- docker-compose.standalone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.standalone.yml b/docker-compose.standalone.yml index 6b459d3..76918e2 100644 --- a/docker-compose.standalone.yml +++ b/docker-compose.standalone.yml @@ -45,7 +45,7 @@ services: - TZ=America/Los_Angeles - AGENCY_ID=unitrans - GTFS_URL=https://unitrans.ucdavis.edu/media/gtfs/Unitrans_GTFS.zip - - GTFS_TIDY_ARGS=v + - GTFS_TIDY_ARGS=OeD ports: # Access the webapp on your host machine at a path like