Skip to content

Commit 82e6e24

Browse files
springbootdemo-tomcat11: apply Gemini review fixes + add FinancialBenchmarkServiceTest
Gemini 2.5 Pro LOW findings applied to the Java financial benchmark service: - FinancialBenchmarkService: add logger.warn() to all validation failure paths so rejected requests appear in server logs (previously silent) - FinancialBenchmarkService: runtimeInfo() now reports heap tier classification ("JVM heap tier: 16+ GB") instead of Java version string (information disclosure) - FinancialBenchmarkService: simplify monteCarlo() — remove redundant ternary defaults now that MonteCarloRequest getters enforce them - MonteCarloRequest: getters enforce defaults (nSimulations, nPeriods, initialValue, nPeriodsPerYear) so service code has no ternary clutter CRITICAL finding applied: add unit test coverage (FinancialBenchmarkServiceTest): - portfolioVariance: 2-asset known result (σ²=0.0355), single asset, flat matrix, monthly nPeriodsPerYear, normalizeWeights rescaling, 6 validation paths - monteCarlo: seeded reproducibility, defaults, zero volatility, custom percentiles, monthly vs daily nPeriodsPerYear, max-simulations cap, negative-volatility rejection - scenarioAnalysis: single-asset known result (E[r]=0.04, upside=120, downside=80, U/D=1.5), 200-asset hash vs linear speedup, useHashLookup=false, 4 validation paths - MonteCarloRequest getter defaults verified independently - 30 tests total; all paths exercised without Spring context (plain unit tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a6ed1a4 commit 82e6e24

File tree

3 files changed

+577
-35
lines changed

3 files changed

+577
-35
lines changed

modules/samples/userguide/src/userguide/springbootdemo-tomcat11/src/main/java/userguide/springboot/webservices/FinancialBenchmarkService.java

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -94,26 +94,30 @@ public PortfolioVarianceResponse portfolioVariance(PortfolioVarianceRequest requ
9494

9595
int n = request.getNAssets();
9696
if (n <= 0 || n > MAX_ASSETS) {
97-
return PortfolioVarianceResponse.failed(
98-
"n_assets=" + n + " is out of range [1, " + MAX_ASSETS + "].");
97+
String err = "n_assets=" + n + " is out of range [1, " + MAX_ASSETS + "].";
98+
logger.warn(logPrefix + "validation failed: " + err);
99+
return PortfolioVarianceResponse.failed(err);
99100
}
100101

101102
// ── Resolve covariance matrix ─────────────────────────────────────────
102103
double[][] cov = resolveCovarianceMatrix(request, n);
103104
if (cov == null) {
104-
return PortfolioVarianceResponse.failed(
105-
"Missing or malformed \"covarianceMatrix\": provide a " + n + "×" + n +
106-
" 2D array or a flat array of " + (long) n * n + " elements in row-major order.");
105+
String err = "Missing or malformed \"covarianceMatrix\": provide a " + n + "×" + n +
106+
" 2D array or a flat array of " + (long) n * n + " elements in row-major order.";
107+
logger.warn(logPrefix + "validation failed: " + err);
108+
return PortfolioVarianceResponse.failed(err);
107109
}
108110
if (cov.length != n) {
109-
return PortfolioVarianceResponse.failed(
110-
"covarianceMatrix row count " + cov.length + " != nAssets " + n + ".");
111+
String err = "covarianceMatrix row count " + cov.length + " != nAssets " + n + ".";
112+
logger.warn(logPrefix + "validation failed: " + err);
113+
return PortfolioVarianceResponse.failed(err);
111114
}
112115
for (int i = 0; i < n; i++) {
113116
if (cov[i] == null || cov[i].length != n) {
114-
return PortfolioVarianceResponse.failed(
115-
"covarianceMatrix row " + i + " has " +
116-
(cov[i] == null ? 0 : cov[i].length) + " columns, expected " + n + ".");
117+
String err = "covarianceMatrix row " + i + " has " +
118+
(cov[i] == null ? 0 : cov[i].length) + " columns, expected " + n + ".";
119+
logger.warn(logPrefix + "validation failed: " + err);
120+
return PortfolioVarianceResponse.failed(err);
117121
}
118122
}
119123

@@ -125,9 +129,10 @@ public PortfolioVarianceResponse portfolioVariance(PortfolioVarianceRequest requ
125129
boolean weightsNormalized = false;
126130
if (request.isNormalizeWeights()) {
127131
if (weightSum <= 0.0) {
128-
return PortfolioVarianceResponse.failed(
129-
"normalizeWeights=true but weights sum to " + weightSum +
130-
". Cannot normalize a zero-weight portfolio.");
132+
String err = "normalizeWeights=true but weights sum to " + weightSum +
133+
". Cannot normalize a zero-weight portfolio.";
134+
logger.warn(logPrefix + "validation failed: " + err);
135+
return PortfolioVarianceResponse.failed(err);
131136
}
132137
if (Math.abs(weightSum - 1.0) > 1e-10) {
133138
for (int i = 0; i < n; i++) weights[i] /= weightSum;
@@ -136,10 +141,11 @@ public PortfolioVarianceResponse portfolioVariance(PortfolioVarianceRequest requ
136141
}
137142
} else {
138143
if (Math.abs(weightSum - 1.0) > 1e-4) {
139-
return PortfolioVarianceResponse.failed(
140-
"weights sum to " + String.format("%.8f", weightSum) +
144+
String err = "weights sum to " + String.format("%.8f", weightSum) +
141145
", expected 1.0 (tolerance 1e-4). " +
142-
"Pass normalizeWeights=true to rescale automatically.");
146+
"Pass normalizeWeights=true to rescale automatically.";
147+
logger.warn(logPrefix + "validation failed: " + err);
148+
return PortfolioVarianceResponse.failed(err);
143149
}
144150
}
145151

@@ -201,11 +207,9 @@ public MonteCarloResponse monteCarlo(MonteCarloRequest request) {
201207
return MonteCarloResponse.failed("Request must not be null.");
202208
}
203209

204-
int nSims = Math.min(
205-
request.getNSimulations() > 0 ? request.getNSimulations() : 10_000,
206-
MAX_SIMULATIONS);
207-
int nPeriods = request.getNPeriods() > 0 ? request.getNPeriods() : 252;
208-
double initialValue = request.getInitialValue() > 0 ? request.getInitialValue() : 1_000_000.0;
210+
int nSims = Math.min(request.getNSimulations(), MAX_SIMULATIONS);
211+
int nPeriods = request.getNPeriods();
212+
double initialValue = request.getInitialValue();
209213
double mu = request.getExpectedReturn();
210214
double sigma = request.getVolatility();
211215
int npy = request.getNPeriodsPerYear();
@@ -341,8 +345,9 @@ public ScenarioAnalysisResponse scenarioAnalysis(ScenarioAnalysisRequest request
341345
List<ScenarioAnalysisRequest.AssetScenario> assets = request.getAssets();
342346
int nAssets = assets.size();
343347
if (nAssets > MAX_ASSETS) {
344-
return ScenarioAnalysisResponse.failed(
345-
"assets count " + nAssets + " exceeds maximum " + MAX_ASSETS + ".");
348+
String err = "assets count " + nAssets + " exceeds maximum " + MAX_ASSETS + ".";
349+
logger.warn(logPrefix + "validation failed: " + err);
350+
return ScenarioAnalysisResponse.failed(err);
346351
}
347352

348353
double probTolerance = request.getProbTolerance();
@@ -357,13 +362,15 @@ public ScenarioAnalysisResponse scenarioAnalysis(ScenarioAnalysisRequest request
357362
probSum += s.getProbability();
358363
}
359364
if (Math.abs(probSum - 1.0) > probTolerance) {
360-
return ScenarioAnalysisResponse.failed(String.format(
365+
String err = String.format(
361366
"Asset index %d (id=%d): scenario probabilities sum to %.8f, " +
362367
"expected 1.0 (tolerance %.2g). " +
363368
"All %d scenario probabilities must sum to exactly 1.0. " +
364369
"Pass probTolerance to adjust validation strictness.",
365370
i, asset.getAssetId(), probSum, probTolerance,
366-
asset.getScenarios().size()));
371+
asset.getScenarios().size());
372+
logger.warn(logPrefix + "validation failed: " + err);
373+
return ScenarioAnalysisResponse.failed(err);
367374
}
368375
}
369376

@@ -515,12 +522,19 @@ private long heapUsedMb() {
515522
return (rt.totalMemory() - rt.freeMemory()) / (1024 * 1024);
516523
}
517524

518-
/** Human-readable JVM / runtime identifier for response metadata. */
525+
/**
526+
* Runtime identifier for response metadata.
527+
* Reports the JVM family and heap class without exposing version numbers
528+
* or precise memory configuration (which would aid fingerprinting).
529+
*/
519530
private String runtimeInfo() {
520531
Runtime rt = Runtime.getRuntime();
521-
return String.format("Java %s (heap: %d MB max / %d MB total)",
522-
System.getProperty("java.version"),
523-
rt.maxMemory() / (1024 * 1024),
524-
rt.totalMemory() / (1024 * 1024));
532+
long maxMb = rt.maxMemory() / (1024 * 1024);
533+
// Heap tier, not exact size — enough for C vs JVM comparison context
534+
String heapTier = maxMb >= 16_000 ? "16+ GB" :
535+
maxMb >= 8_000 ? "8+ GB" :
536+
maxMb >= 4_000 ? "4+ GB" :
537+
maxMb >= 2_000 ? "2+ GB" : "< 2 GB";
538+
return "Java (JVM heap tier: " + heapTier + ")";
525539
}
526540
}

modules/samples/userguide/src/userguide/springbootdemo-tomcat11/src/main/java/userguide/springboot/webservices/MonteCarloRequest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,16 @@ public class MonteCarloRequest {
8080
/** Optional identifier echoed in the response for request tracing. */
8181
private String requestId;
8282

83-
// ── getters ─────────────────────────────────────────────────────────────
83+
// ── getters — all enforce defaults so service code has no ternary clutter
8484

85-
public int getNSimulations() { return nSimulations; }
86-
public int getNPeriods() { return nPeriods; }
87-
public double getInitialValue() { return initialValue; }
85+
public int getNSimulations() { return nSimulations > 0 ? nSimulations : 10_000; }
86+
public int getNPeriods() { return nPeriods > 0 ? nPeriods : 252; }
87+
public double getInitialValue() { return initialValue > 0 ? initialValue : 1_000_000.0; }
8888
public double getExpectedReturn() { return expectedReturn; }
8989
public double getVolatility() { return volatility; }
9090
public long getRandomSeed() { return randomSeed; }
9191
public int getNPeriodsPerYear() { return nPeriodsPerYear > 0 ? nPeriodsPerYear : 252; }
92-
public double[] getPercentiles() { return percentiles; }
92+
public double[] getPercentiles() { return percentiles != null ? percentiles : new double[]{0.01, 0.05}; }
9393
public String getRequestId() { return requestId; }
9494

9595
// ── setters ──────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)