Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sap-demo-java/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ k8s/secret.yaml
/tmp
README.md
demo_script.sh
simulate_tosca_flow.sh
simulate_fiori_flow.sh
deploy_kind.sh
402 changes: 166 additions & 236 deletions sap-demo-java/README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion sap-demo-java/demo_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fail() { printf "${BOLD}${RED}XX %s${NC}\n" "$*"; }
BASE_URL="${BASE_URL:-http://localhost:30080}"

# ----------------------------------------------------------------------------
# exercise_endpoints — the scripted "Tosca-style" business flow that drives
# exercise_endpoints — the scripted UI-style business flow that drives
# the service while Keploy records underneath. 1 inbound → many SAP calls.
# ----------------------------------------------------------------------------
exercise_endpoints() {
Expand Down
11 changes: 5 additions & 6 deletions sap-demo-java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,16 @@
<relativePath/>
</parent>

<groupId>com.tricentisdemo.sap</groupId>
<groupId>com.keploy.sapdemo</groupId>
<artifactId>customer360</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>SAP Customer 360 Service</name>
<description>
Enterprise-grade Spring Boot integration service that aggregates SAP Business Partner
data from multiple S/4HANA OData endpoints into a unified Customer 360 view.
Designed as a reference implementation for RISE with SAP landscapes — the kind of
BTP-style integration middleware Tricentis customers build and must regression-test
during ECC → S/4HANA Cloud migrations.
Spring Boot integration service that aggregates SAP Business Partner data from multiple
S/4HANA OData endpoints into a unified Customer 360 view, merged with locally stored
CRM tags and notes from Postgres. Used as a regression-testing sample for Keploy's SAP
fan-out handling and its HTTPS + Postgres parsers.
</description>

<properties>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# simulate_tosca_flow.sh — narrated "Tosca drives Fiori; Keploy records the
# simulate_fiori_flow.sh — narrated "UI drives Fiori; Keploy records the
# 3-way fan-out underneath" demo for the Customer 360 service.
#
# Run this while `keploy record` is recording the service in another terminal.
Expand Down Expand Up @@ -29,8 +29,8 @@ BOLD='\033[1m'; DIM='\033[2m'; GREEN='\033[0;32m'; BLUE='\033[0;34m'
YELLOW='\033[1;33m'; MAGENTA='\033[0;35m'; NC='\033[0m'

pause() { [ "$FAST" = "1" ] || sleep "${1:-2}"; }
tosca_step() { printf "\n${BOLD}${MAGENTA}[TOSCA UI]${NC} %s\n" "$1"; pause 1; }
tosca_click() { printf "${DIM} ↳ clicks:${NC} %s\n" "$1"; pause 1; }
ui_step() { printf "\n${BOLD}${MAGENTA}[FIORI UI]${NC} %s\n" "$1"; pause 1; }
ui_click() { printf "${DIM} ↳ clicks:${NC} %s\n" "$1"; pause 1; }
backend() { printf "${BOLD}${BLUE}[KEPLOY ]${NC} ${DIM}%s${NC}\n" "$1"; }

call() {
Expand All @@ -55,7 +55,7 @@ check_reachable() {
banner() {
printf "\n${BOLD}"
printf "═══════════════════════════════════════════════════════════════════\n"
printf " Simulated Tosca-driven Fiori flow: 'Customer 360 in Sales Cockpit'\n"
printf " Simulated Fiori-driven flow: 'Customer 360 in Sales Cockpit'\n"
printf " Keploy records every outbound SAP OData call in parallel.\n"
printf " Target: ${HOST}\n"
printf "═══════════════════════════════════════════════════════════════════${NC}\n"
Expand All @@ -66,56 +66,56 @@ banner
pause 2

# ─────────────────────────────────────────────────────────────────────────────
tosca_step "Opening Sales Cockpit → clicking 'Customers' tile"
tosca_click "Customers launchpad tile"
ui_step "Opening Sales Cockpit → clicking 'Customers' tile"
ui_click "Customers launchpad tile"
backend "Tile click fans out: list query + KPI count"
call "GET /api/v1/customers/count (KPI tile)" "${HOST}/api/v1/customers/count"
pause 2
call "GET /api/v1/customers?top=5 (list grid)" "${HOST}/api/v1/customers?top=5"
pause 2

# ─────────────────────────────────────────────────────────────────────────────
tosca_step "On the customer list, opening row BP=11"
tosca_click "Row BusinessPartner=11"
ui_step "On the customer list, opening row BP=11"
ui_click "Row BusinessPartner=11"
backend "Detail fetch — single SAP OData GET"
call "GET /api/v1/customers/11 (detail)" "${HOST}/api/v1/customers/11"
pause 2

# ─────────────────────────────────────────────────────────────────────────────
tosca_step "User clicks '360° view' on BP=202 — this is the fan-out moment"
tosca_click "360° view button"
ui_step "User clicks '360° view' on BP=202 — this is the fan-out moment"
ui_click "360° view button"
backend "ONE inbound → THREE parallel SAP OData calls:"
backend " • /A_BusinessPartner('202')"
backend " • /A_BusinessPartner('202')/to_BusinessPartnerAddress"
backend " • /A_BusinessPartner('202')/to_BusinessPartnerRole"
backend "Tosca asserts on the UI tile. Keploy captures all three on the wire."
backend "UI tests assert on the surface. Keploy captures all three on the wire."
call "GET /api/v1/customers/202/360 (360 fan-out)" "${HOST}/api/v1/customers/202/360"
pause 3

# ─────────────────────────────────────────────────────────────────────────────
tosca_step "Drilling into a second customer — BP=11 360"
tosca_click "Back → select BP=11 → 360° view"
ui_step "Drilling into a second customer — BP=11 360"
ui_click "Back → select BP=11 → 360° view"
call "GET /api/v1/customers/11/360 (360 fan-out)" "${HOST}/api/v1/customers/11/360"
pause 2

# ─────────────────────────────────────────────────────────────────────────────
printf "\n${BOLD}${GREEN}"
printf "═══════════════════════════════════════════════════════════════════\n"
printf " Tosca flow complete. What happened in two panes:\n"
printf " Fiori flow complete. What happened in two panes:\n"
printf "═══════════════════════════════════════════════════════════════════${NC}\n\n"

cat <<'EOF'
┌─────────────────────────────┬─────────────────────────────────────────┐
TOSCA (this terminal) │ KEPLOY (other terminal) │
UI LAYER (this terminal) │ KEPLOY (other terminal) │
├─────────────────────────────┼─────────────────────────────────────────┤
│ • 5 Fiori interactions │ • 5 inbound HTTP test cases captured │
│ • Asserted on UI state │ • ~11 outbound SAP OData mocks │
│ • Zero backend visibility │ (2× 360 fan-out = 6 mocks alone) │
│ │ • Full vertical slice: UI click→DB row │
└─────────────────────────────┴─────────────────────────────────────────┘

One UI flow, two coverage layers. Tosca owns the surface. Keploy owns
the plumbing Tosca could not see before — especially the hidden parallel
One UI flow, two coverage layers. UI suites own the surface. Keploy owns
the plumbing they could not see before — especially the hidden parallel
fan-out behind the 360° tile.

Stop Keploy in its terminal, then:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360;
package com.keploy.sapdemo.customer360;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
Expand All @@ -20,9 +20,11 @@
* assigned roles — aggregated into a flat response.
*
* In a typical RISE with SAP landscape this would run as a BTP extension
* (Cloud Foundry or Kyma/Kubernetes). It's the kind of service that Tricentis
* LiveCompare flags as "impacted by migration" and that teams must regression-
* test end-to-end after every S/4HANA quarterly update.
* (Cloud Foundry or Kyma/Kubernetes). It's the kind of service teams
* regression-test end-to-end after every S/4HANA quarterly update, and the
* workload Keploy uses to validate its SAP fan-out handling (parallel
* outbound TLS + keep-alive + chunked responses) alongside its Postgres
* parser.
*/
@SpringBootApplication
@EnableAsync
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.tricentisdemo.sap.customer360.config;
package com.keploy.sapdemo.customer360.config;

import com.tricentisdemo.sap.customer360.sap.CorrelationIdInterceptor;
import com.keploy.sapdemo.customer360.sap.CorrelationIdInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -34,7 +34,7 @@
* </ol>
*
* <p>An {@link Executor} bean is also exposed for the
* {@link com.tricentisdemo.sap.customer360.service.Customer360AggregatorService}
* {@link com.keploy.sapdemo.customer360.service.Customer360AggregatorService}
* fan-out pattern. The three SAP OData calls run in parallel; the pool is
* intentionally small to avoid overwhelming the SAP API manager during
* regression test runs.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.model;
package com.keploy.sapdemo.customer360.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand All @@ -11,8 +11,8 @@
* {@code ignoreUnknown = true} so a downstream addition of a new field by
* SAP doesn't break this client — but a <em>removal</em> or <em>rename</em>
* of a consumed field will surface as a missing value during
* {@code keploy test}, which is exactly the contract-drift signal
* Tricentis customers need after quarterly S/4HANA updates.
* {@code keploy test}, which is exactly the contract-drift signal teams
* need after quarterly S/4HANA updates.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class BusinessPartner {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.model;
package com.keploy.sapdemo.customer360.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.model;
package com.keploy.sapdemo.customer360.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.tricentisdemo.sap.customer360.model;
package com.keploy.sapdemo.customer360.model;

import com.tricentisdemo.sap.customer360.persistence.CustomerNote;
import com.tricentisdemo.sap.customer360.persistence.CustomerTag;
import com.keploy.sapdemo.customer360.persistence.CustomerNote;
import com.keploy.sapdemo.customer360.persistence.CustomerTag;

import java.time.Instant;
import java.util.List;
Expand All @@ -10,7 +10,7 @@
* The aggregated "Customer 360" response returned to downstream consumers.
*
* <p>Produced by
* {@link com.tricentisdemo.sap.customer360.service.Customer360AggregatorService}
* {@link com.keploy.sapdemo.customer360.service.Customer360AggregatorService}
* from <b>three parallel SAP OData calls + two parallel Postgres queries +
* one audit INSERT</b>. This composite shape is the kind of payload
* downstream CRM / portal / analytics pipelines consume in typical RISE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.model;
package com.keploy.sapdemo.customer360.model;

/**
* Flat, list-friendly projection of a {@link BusinessPartner} for the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.model;
package com.keploy.sapdemo.customer360.model;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.model;
package com.keploy.sapdemo.customer360.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.model;
package com.keploy.sapdemo.customer360.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand All @@ -14,7 +14,7 @@
* returns the entity at the root — when this service is migrated to v4 APIs
* (e.g., {@code /API_BUSINESS_PARTNER_SRV/A_BusinessPartner('11')} in
* S/4HANA Cloud), this envelope goes away. That shift is the exact kind of
* change a Tricentis migration customer has to regression-test.
* change migration teams have to regression-test.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class ODataEntityResponse<T> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.model;
package com.keploy.sapdemo.customer360.model;

import com.fasterxml.jackson.annotation.JsonInclude;

Expand All @@ -7,7 +7,7 @@
/**
* RFC 7807 Problem Details for HTTP APIs response body.
*
* <p>Returned by {@link com.tricentisdemo.sap.customer360.web.GlobalExceptionHandler}
* <p>Returned by {@link com.keploy.sapdemo.customer360.web.GlobalExceptionHandler}
* for any unhandled exception surfaced by the service. Stable shape so that
* downstream consumers (and Keploy mock diffs) can rely on it.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.model;
package com.keploy.sapdemo.customer360.model;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.persistence;
package com.keploy.sapdemo.customer360.persistence;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.persistence;
package com.keploy.sapdemo.customer360.persistence;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.persistence;
package com.keploy.sapdemo.customer360.persistence;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.tricentisdemo.sap.customer360.repository;
package com.keploy.sapdemo.customer360.repository;

import com.tricentisdemo.sap.customer360.persistence.AuditEvent;
import com.keploy.sapdemo.customer360.persistence.AuditEvent;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.tricentisdemo.sap.customer360.repository;
package com.keploy.sapdemo.customer360.repository;

import com.tricentisdemo.sap.customer360.persistence.CustomerNote;
import com.keploy.sapdemo.customer360.persistence.CustomerNote;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.tricentisdemo.sap.customer360.repository;
package com.keploy.sapdemo.customer360.repository;

import com.tricentisdemo.sap.customer360.persistence.CustomerTag;
import com.keploy.sapdemo.customer360.persistence.CustomerTag;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.sap;
package com.keploy.sapdemo.customer360.sap;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.sap;
package com.keploy.sapdemo.customer360.sap;

import org.slf4j.MDC;
import org.springframework.http.HttpRequest;
Expand All @@ -12,7 +12,7 @@
/**
* Propagates the caller's correlation id into every outbound SAP call.
*
* <p>For incoming requests, {@link com.tricentisdemo.sap.customer360.sap.CorrelationIdFilter}
* <p>For incoming requests, {@link com.keploy.sapdemo.customer360.sap.CorrelationIdFilter}
* seeds the MDC. This interceptor reads it back and sets the
* {@code X-Correlation-ID} header on the SAP call so distributed traces
* chain across the hop — operationally critical in BTP landscapes where a
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tricentisdemo.sap.customer360.sap;
package com.keploy.sapdemo.customer360.sap;

import org.springframework.http.HttpStatus;

Expand All @@ -16,7 +16,7 @@
* </ul>
*
* <p>The {@link #upstreamStatus} field preserves the exact status SAP returned
* so {@link com.tricentisdemo.sap.customer360.web.GlobalExceptionHandler}
* so {@link com.keploy.sapdemo.customer360.web.GlobalExceptionHandler}
* can surface it in an RFC 7807 problem response as
* {@code X-Upstream-Status}. Keploy captures this header verbatim in the
* replayed mocks, which lets contract-diff checks catch status regressions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.tricentisdemo.sap.customer360.sap;
package com.keploy.sapdemo.customer360.sap;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tricentisdemo.sap.customer360.model.BusinessPartner;
import com.tricentisdemo.sap.customer360.model.BusinessPartnerAddress;
import com.tricentisdemo.sap.customer360.model.BusinessPartnerRole;
import com.tricentisdemo.sap.customer360.model.ODataCollectionResponse;
import com.tricentisdemo.sap.customer360.model.ODataEntityResponse;
import com.keploy.sapdemo.customer360.model.BusinessPartner;
import com.keploy.sapdemo.customer360.model.BusinessPartnerAddress;
import com.keploy.sapdemo.customer360.model.BusinessPartnerRole;
import com.keploy.sapdemo.customer360.model.ODataCollectionResponse;
import com.keploy.sapdemo.customer360.model.ODataEntityResponse;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import org.slf4j.Logger;
Expand Down Expand Up @@ -41,7 +41,7 @@
*
* <p>The service path is fixed; only the sub-path varies. Base URL is set
* on the RestTemplate's rootUri (see
* {@link com.tricentisdemo.sap.customer360.config.SapClientConfig}).
* {@link com.keploy.sapdemo.customer360.config.SapClientConfig}).
*/
@Component
public class SapBusinessPartnerClient {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.tricentisdemo.sap.customer360.service;
package com.keploy.sapdemo.customer360.service;

import com.tricentisdemo.sap.customer360.persistence.AuditEvent;
import com.tricentisdemo.sap.customer360.repository.AuditEventRepository;
import com.tricentisdemo.sap.customer360.sap.CorrelationIdInterceptor;
import com.keploy.sapdemo.customer360.persistence.AuditEvent;
import com.keploy.sapdemo.customer360.repository.AuditEventRepository;
import com.keploy.sapdemo.customer360.sap.CorrelationIdInterceptor;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down
Loading
Loading