This is an automated email from the ASF dual-hosted git repository.
meonkeys pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/fineract-site.git
The following commit(s) were added to refs/heads/asf-site by this push:
new 61da315 update /docs/1.16.0-SNAPSHOT
61da315 is described below
commit 61da3153d8d32935628940a7b6da79e86fbd9069
Author: Adam Monsen <[email protected]>
AuthorDate: Mon Aug 31 09:47:58 2026 -0700
update /docs/1.16.0-SNAPSHOT
docs built with `./gradlew asciidoctor` from
https://github.com/apache/fineract/commit/ef8ae79d4e39cca9a503a19e62b94efc6c7140e2
change made with `fineract-site docs`
Rebuilt so soon after 8120910dac35dead0b53b91ca9e0564fd2d5a550 because I
noticed that build was missing a couple Vega-Lite diagrams... I built locally
and ignored these messages:
> Task :fineract-doc:asciidoctor
Failed to generate image: Could not find the 'vg2svg' executable in
PATH; add it to the PATH or specify its location using the 'vg2svg' document
attribute :: ../../../build/generated/diagrams/main-sequence.vl.json ::
/home/adamm/git/apache/fineract/fineract-doc/build/generated/diagrams//home/adamm/git/apache/fineract/fineract-doc/build/generated/diagrams/main-sequence.vl.json:0
(uri:classloader:/gems/asciidoctor-2.0.18/lib/asciidoctor/parser.rb:build_block)
Failed to generate image: no implicit conversion of nil into String ::
../../../build/generated/diagrams/distance-ranking.vl.json ::
/home/adamm/git/apache/fineract/fineract-doc/build/generated/diagrams//home/adamm/git/apache/fineract/fineract-doc/build/generated/diagrams/distance-ranking.vl.json:0
(uri:classloader:/gems/asciidoctor-2.0.18/lib/asciidoctor/parser.rb:build_block)
Failed to generate image: no implicit conversion of nil into String ::
../../../build/generated/diagrams/cross-feature-matrix.vl.json ::
/home/adamm/git/apache/fineract/fineract-doc/build/generated/diagrams//home/adamm/git/apache/fineract/fineract-doc/build/generated/diagrams/cross-feature-matrix.vl.json:0
(uri:classloader:/gems/asciidoctor-2.0.18/lib/asciidoctor/parser.rb:build_block)
And under "Abstractness against instability, with the main sequence" I
missed the raw JSON with "Failed to generate image: Could not find the 'vl2vg'
executable in PATH; add it to the PATH or specify its location using the
'vl2vg' document attribute".
Note: Today I also wrote
https://github.com/apache/fineract/pull/6315#issuecomment-5481448575 and filed
https://issues.apache.org/jira/browse/FINERACT-2795 .
---
docs/1.16.0-SNAPSHOT/index.html | 1799 ++++++++++++++++-----------------------
1 file changed, 711 insertions(+), 1088 deletions(-)
diff --git a/docs/1.16.0-SNAPSHOT/index.html b/docs/1.16.0-SNAPSHOT/index.html
index f816785..4aa85cb 100644
--- a/docs/1.16.0-SNAPSHOT/index.html
+++ b/docs/1.16.0-SNAPSHOT/index.html
@@ -846,6 +846,7 @@ pre.rouge {
<li><a href="#testing-cucumber">Cucumber E2E Tests</a></li>
<li><a href="#_unit_testing">Unit Testing</a></li>
<li><a href="#_integration_testing">Integration Testing</a></li>
+<li><a href="#_feign_integration_tests">Feign Integration Tests</a></li>
</ul>
</li>
<li><a href="#_fineract_documentation_guide">Fineract Documentation Guide</a>
@@ -925,7 +926,7 @@ Email: <a
href="mailto:[email protected]">[email protected]</a></p>
<p><strong>Version</strong>: 1.16.0-SNAPSHOT</p>
</div>
<div class="paragraph">
-<p><strong>Built on</strong>: Sat Aug 29 20:04:15 PDT 2026</p>
+<p><strong>Built on</strong>: Mon Aug 31 09:19:07 PDT 2026</p>
</div>
<div class="paragraph">
<p>Note the version in the URL path:</p>
@@ -1207,7 +1208,7 @@ To overcome this problem, Fineract instances can be
started in different instanc
</div>
<div class="imageblock">
<div class="content">
-<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAAD8CAIAAADsRU8RAAAdR0lEQVR4Xu2deXRV1dmHLwEDREYTBqUC1iIWKFBqZagFKwtCyQqEoYgUNYupBQErIp8MMrlqkQY+pCAQJikYFFOLtAkQBMkAkSE2EaKEOSEDGZgSyEAS8/163nX3vewMHC73XPbO9z5/ZJ1hn3P3ft/n7L1P7smJrYJhtMImb2AYtWFlGc1wKPsDwygMK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toRq1S1tfX18aYA7GSw6cJtUpZZEK0gqkZxOr69esFBQWFhYUlJSVlZWVyNFXF
[...]
+<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAAD8CAIAAADsRU8RAAAb6UlEQVR4Xu2de3gU1fmAlyCXXDFNCDwIiMWAAgXKk3Ix3ISHgpeICkKBolgQqoCCotZauVipPIp4oYhYAgqKFS+IAspFQYxco0VRASlIICYRJAmJJgQS83ufOd2z64TAkOyuc/L73j/ynJk5M3vO9717zpnNZOMpFwSj8Nh3CIK7EWUFw/Ap+5MguBhRVjAMUVYwDFFWMAxRVjAMUVYwDFFWMAxRVjAMUVYwDFFWMAxRVjAMUVYwDFFWMAxRVjAMUVYwDFFWMAxRVjAMUVYwDFFWMAxRVjAMUVYwDFFWMAxRVjAMUVYwDFFWMAxRVjAMUVYwDFFWMAxRVjAMUVYwDFFWMAxRVjCMGqVsXFycR3AGsbKHzxBqlLJkQvdCODvEKj8/v7CwsKioqKSkpLS01B5Nt+Lr
[...]
</div>
</div>
</div>
@@ -1219,7 +1220,7 @@ In this case, the write instance and the database will be
freed from part of the
</div>
<div class="imageblock">
<div class="content">
-<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAuQAAAFCCAIAAAA2a19cAAA+5ElEQVR4Xu3deZhUxbk/8AZUFtk3cQMS4xJcIMarogZErmLgAREIwZ0LSsQAxiWoYGSJMaiIRlERwRURFRPlXkQ2o8gSVAQEVFCQQfZ9HZZB+X1//T5T51A909ZwuqZOdX8/f/Cc7q4+c7reb/V5u6eZThwiIiIiirGEfgURERFRnLBZISIiolgLmpUfiYiIiGKDzQoRERHFGpsVIiIiijU2K0RERBRrbFaIiIgo1tisEBERUayxWSEiIqJYY7NCREREscZmhYiIiGKNzQoRERHFGpsVIiIiijU2K0RERBRrbFaIiIgo1tisEBERUayxWSEiIqJYY7NCREREscZmhYiIiGKNzQoRERHFGpsVIiIiijU2K0RERBRrbFaIiIgo1tisEBERUayxWSEiIqJYY7NCRERE
[...]
+<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAuQAAAFCCAIAAAA2a19cAAA8lklEQVR4Xu3deZxUxbk+8AbFYV8EwRC2hE1HBOLlsogLyMegEtxAFIjbBcEFXFFcbhw0mhBBicGViCDiLlGTIAKuiCCKiqIiGpBRZBUYQBhGlvk9t+s3dY7VM2PB6ZqqOuf5/sGnprv6zOl6n+rz9ixMqpiIiIjIYSn1BiIiIiKXsFkhIiIipwXNyj4iIiIiZ7BZISIiIqexWSEiIiKnsVkhIiIip7FZISIiIqexWSEiIiKnsVkhIiIip7FZISIiIqexWSEiIiKnsVkhIiIip7FZISIiIqexWSEiIiKnsVkhIiIip7FZISIiIqexWSEiIiKnsVkhIiIip7FZISIiIqexWSEiIiKnsVkhIiIip7FZISIiIqexWSEiIiKnsVkhIiIip7FZISIiIqexWSEiIiKnsVkh
[...]
</div>
</div>
<div class="paragraph">
@@ -1230,7 +1231,7 @@ The best practice is to deploy 1 master batch instance
and multiple worker batch
</div>
<div class="imageblock">
<div class="content">
-<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAAEYCAIAAACLF699AAA3lklEQVR4Xu3deZhU1Z3/8QsiAjabLBJNBHU0CTDAMEbBUYkyAgMDIhhExYQHhERFNG4/QUTQGUcd9GccVARRcQFDSOISQHCJNJsIKMgiIIusLfvS0gs06flOnafPLQ/dnUOdKqpO3ffrD55bVadud5/vpy+f3oNSAAAAIE0C8w4AAADgZKGMAgAAIG3CMvo3AAAA4KSgjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAA
[...]
+<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAAEYCAIAAACLF699AAA1CUlEQVR4Xu3de3hU1b3/8QHlHm5NBA8C1cpFEIHDw+EiyEWOoli8oaJQLR5QqoJFpd5aUbS2PNZqrRcUBRUVDlZFVEDQKmBUULQoKqCiRC7hDoIQAsH8vr9ZJ2uPKwldzMpkZu39fv3Bs2Zmzc5kfz/Z+SQQEisGAAAA0iRm3gEAAABUFsooAAAA0iYooz8CAAAAlYIyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA
[...]
</div>
</div>
<div class="paragraph">
@@ -4192,951 +4193,23 @@ column B <strong>and</strong> row B / column A — is a
<strong>cycle</strong>,
the other. A column that is almost entirely filled marks a feature the whole
platform<br>
depends on, which needs stability rather than decoupling.</p>
</div>
-<div class="listingblock">
-<div class="title">Abstractness against instability, with the main
sequence</div>
+<div class="imageblock">
<div class="content">
-<pre>Failed to generate image: Could not find the 'vl2vg' executable in PATH;
add it to the PATH or specify its location using the 'vl2vg' document attribute
-{
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
- "title": "Abstractness vs Instability (distance from the main sequence)",
- "width": 520,
- "height": 420,
- "data": {
- "values": [
- {
- "component": "loan",
- "A": 0.1181,
- "I": 0.5077,
- "D": 0.3742,
- "classCount": 1507
- },
- {
- "component": "account-transfers",
- "A": 0.0742,
- "I": 0.6916,
- "D": 0.2342,
- "classCount": 229
- },
- {
- "component": "client",
- "A": 0.1087,
- "I": 0.4533,
- "D": 0.438,
- "classCount": 276
- },
- {
- "component": "savings",
- "A": 0.069,
- "I": 0.5927,
- "D": 0.3383,
- "classCount": 783
- },
- {
- "component": "accounting",
- "A": 0.1101,
- "I": 0.556,
- "D": 0.334,
- "classCount": 318
- },
- {
- "component": "group",
- "A": 0.0657,
- "I": 0.6106,
- "D": 0.3237,
- "classCount": 274
- },
- {
- "component": "organisation",
- "A": 0.126,
- "I": 0.109,
- "D": 0.7651,
- "classCount": 254
- },
- {
- "component": "shareaccounts",
- "A": 0.1308,
- "I": 0.7829,
- "D": 0.0862,
- "classCount": 107
- },
- {
- "component": "charge",
- "A": 0.0533,
- "I": 0.2821,
- "D": 0.6646,
- "classCount": 75
- },
- {
- "component": "notifications",
- "A": 0.1897,
- "I": 0.9667,
- "D": 0.1563,
- "classCount": 58
- },
- {
- "component": "calendar",
- "A": 0.12,
- "I": 0.3667,
- "D": 0.5133,
- "classCount": 50
- },
- {
- "component": "batch",
- "A": 0.0222,
- "I": 0.7241,
- "D": 0.2536,
- "classCount": 90
- },
- {
- "component": "teller",
- "A": 0.0875,
- "I": 0.9643,
- "D": 0.0518,
- "classCount": 80
- },
- {
- "component": "rate",
- "A": 0.125,
- "I": 0.6341,
- "D": 0.2409,
- "classCount": 24
- },
- {
- "component": "useradministration",
- "A": 0.124,
- "I": 0.1294,
- "D": 0.7466,
- "classCount": 121
- },
- {
- "component": "report",
- "A": 0.6667,
- "I": 0.3333,
- "D": 0.0,
- "classCount": 6
- },
- {
- "component": "template",
- "A": 0.1154,
- "I": 0.75,
- "D": 0.1346,
- "classCount": 52
- }
- ]
- },
- "layer": [
- {
- "data": {
- "values": [
- {
- "I": 0,
- "A": 1
- },
- {
- "I": 1,
- "A": 0
- }
- ]
- },
- "mark": {
- "type": "line",
- "strokeDash": [
- 6,
- 4
- ],
- "color": "#888"
- },
- "encoding": {
- "x": {
- "field": "I",
- "type": "quantitative"
- },
- "y": {
- "field": "A",
- "type": "quantitative"
- }
- }
- },
- {
- "mark": {
- "type": "circle",
- "opacity": 0.8
- },
- "encoding": {
- "x": {
- "field": "I",
- "type": "quantitative",
- "title": "Instability (I = Ce / (Ca + Ce))",
- "scale": {
- "domain": [
- 0,
- 1
- ]
- }
- },
- "y": {
- "field": "A",
- "type": "quantitative",
- "title": "Abstractness (A)",
- "scale": {
- "domain": [
- 0,
- 1
- ]
- }
- },
- "size": {
- "field": "classCount",
- "type": "quantitative",
- "title": "classes"
- },
- "color": {
- "field": "D",
- "type": "quantitative",
- "title": "Distance",
- "scale": {
- "scheme": "orangered"
- }
- },
- "tooltip": [
- {
- "field": "component",
- "type": "nominal"
- },
- {
- "field": "A",
- "type": "quantitative"
- },
- {
- "field": "I",
- "type": "quantitative"
- },
- {
- "field": "D",
- "type": "quantitative"
- },
- {
- "field": "classCount",
- "type": "quantitative"
- }
- ]
- }
- },
- {
- "mark": {
- "type": "text",
- "dy": -10,
- "fontSize": 9
- },
- "encoding": {
- "x": {
- "field": "I",
- "type": "quantitative"
- },
- "y": {
- "field": "A",
- "type": "quantitative"
- },
- "text": {
- "field": "component",
- "type": "nominal"
- }
- }
- }
- ]
-}</pre>
+<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz4KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj48c3ZnIGNsYXNzPSdtYXJrcycgaGVpZ2h0PSc0ODQnIHByZXNlcnZlQXNwZWN0UmF0aW89J3hNaWRZTWlkIG1lZXQnIHZlcnNpb249JzEuMScgdmlld0JveD0nMCAwIDYzNSA0ODQnIHdpZHRoPSc2MzUnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPjxyZWN0IGZpbGw9
[...]
</div>
+<div class="title">Figure 4. Abstractness against instability, with the main
sequence</div>
</div>
-<div class="listingblock">
-<div class="title">Distance from the main sequence, by feature</div>
+<div class="imageblock">
<div class="content">
-<pre>Failed to generate image:
-{
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
- "title": "Distance from the main sequence, by component",
- "width": 520,
- "height": {
- "step": 14
- },
- "data": {
- "values": [
- {
- "component": "loan",
- "D": 0.3742,
- "Ce": 461,
- "Ca": 447
- },
- {
- "component": "account-transfers",
- "D": 0.2342,
- "Ce": 148,
- "Ca": 66
- },
- {
- "component": "client",
- "D": 0.438,
- "Ce": 160,
- "Ca": 193
- },
- {
- "component": "savings",
- "D": 0.3383,
- "Ce": 227,
- "Ca": 156
- },
- {
- "component": "accounting",
- "D": 0.334,
- "Ce": 154,
- "Ca": 123
- },
- {
- "component": "group",
- "D": 0.3237,
- "Ce": 138,
- "Ca": 88
- },
- {
- "component": "organisation",
- "D": 0.7651,
- "Ce": 73,
- "Ca": 597
- },
- {
- "component": "shareaccounts",
- "D": 0.0862,
- "Ce": 101,
- "Ca": 28
- },
- {
- "component": "charge",
- "D": 0.6646,
- "Ce": 55,
- "Ca": 140
- },
- {
- "component": "notifications",
- "D": 0.1563,
- "Ce": 58,
- "Ca": 2
- },
- {
- "component": "calendar",
- "D": 0.5133,
- "Ce": 44,
- "Ca": 76
- },
- {
- "component": "batch",
- "D": 0.2536,
- "Ce": 42,
- "Ca": 16
- },
- {
- "component": "teller",
- "D": 0.0518,
- "Ce": 54,
- "Ca": 2
- },
- {
- "component": "rate",
- "D": 0.2409,
- "Ce": 26,
- "Ca": 15
- },
- {
- "component": "useradministration",
- "D": 0.7466,
- "Ce": 51,
- "Ca": 343
- },
- {
- "component": "report",
- "D": 0.0,
- "Ce": 4,
- "Ca": 8
- },
- {
- "component": "template",
- "D": 0.1346,
- "Ce": 12,
- "Ca": 4
- }
- ]
- },
- "mark": "bar",
- "encoding": {
- "y": {
- "field": "component",
- "type": "nominal",
- "sort": "-x",
- "title": null
- },
- "x": {
- "field": "D",
- "type": "quantitative",
- "title": "Distance (D = |A + I - 1|)"
- },
- "color": {
- "field": "D",
- "type": "quantitative",
- "legend": null,
- "scale": {
- "scheme": "orangered"
- }
- },
- "tooltip": [
- {
- "field": "component",
- "type": "nominal"
- },
- {
- "field": "D",
- "type": "quantitative"
- },
- {
- "field": "Ce",
- "type": "quantitative"
- },
- {
- "field": "Ca",
- "type": "quantitative"
- }
- ]
- }
-}</pre>
+<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz4KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj48c3ZnIGNsYXNzPSdtYXJrcycgaGVpZ2h0PScyOTcnIHByZXNlcnZlQXNwZWN0UmF0aW89J3hNaWRZTWlkIG1lZXQnIHZlcnNpb249JzEuMScgdmlld0JveD0nMCAwIDYyOCAyOTcnIHdpZHRoPSc2MjgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPjxyZWN0IGZpbGw9
[...]
</div>
+<div class="title">Figure 5. Distance from the main sequence, by feature</div>
</div>
-<div class="listingblock">
-<div class="title">Cross-feature dependencies — the row depends on the
column</div>
+<div class="imageblock">
<div class="content">
-<pre>Failed to generate image:
-{
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
- "title": "Cross-feature dependencies (row depends on column)",
- "width": 520,
- "height": 520,
- "data": {
- "values": [
- {
- "from": "loan",
- "to": "account-transfers",
- "v": 1
- },
- {
- "from": "loan",
- "to": "accounting",
- "v": 1
- },
- {
- "from": "loan",
- "to": "calendar",
- "v": 1
- },
- {
- "from": "loan",
- "to": "charge",
- "v": 1
- },
- {
- "from": "loan",
- "to": "client",
- "v": 1
- },
- {
- "from": "loan",
- "to": "group",
- "v": 1
- },
- {
- "from": "loan",
- "to": "organisation",
- "v": 1
- },
- {
- "from": "loan",
- "to": "rate",
- "v": 1
- },
- {
- "from": "loan",
- "to": "savings",
- "v": 1
- },
- {
- "from": "loan",
- "to": "teller",
- "v": 1
- },
- {
- "from": "loan",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "account-transfers",
- "to": "batch",
- "v": 1
- },
- {
- "from": "account-transfers",
- "to": "calendar",
- "v": 1
- },
- {
- "from": "account-transfers",
- "to": "client",
- "v": 1
- },
- {
- "from": "account-transfers",
- "to": "group",
- "v": 1
- },
- {
- "from": "account-transfers",
- "to": "loan",
- "v": 1
- },
- {
- "from": "account-transfers",
- "to": "organisation",
- "v": 1
- },
- {
- "from": "account-transfers",
- "to": "savings",
- "v": 1
- },
- {
- "from": "account-transfers",
- "to": "shareaccounts",
- "v": 1
- },
- {
- "from": "account-transfers",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "client",
- "to": "account-transfers",
- "v": 1
- },
- {
- "from": "client",
- "to": "accounting",
- "v": 1
- },
- {
- "from": "client",
- "to": "charge",
- "v": 1
- },
- {
- "from": "client",
- "to": "group",
- "v": 1
- },
- {
- "from": "client",
- "to": "loan",
- "v": 1
- },
- {
- "from": "client",
- "to": "organisation",
- "v": 1
- },
- {
- "from": "client",
- "to": "savings",
- "v": 1
- },
- {
- "from": "client",
- "to": "shareaccounts",
- "v": 1
- },
- {
- "from": "client",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "savings",
- "to": "account-transfers",
- "v": 1
- },
- {
- "from": "savings",
- "to": "accounting",
- "v": 1
- },
- {
- "from": "savings",
- "to": "calendar",
- "v": 1
- },
- {
- "from": "savings",
- "to": "charge",
- "v": 1
- },
- {
- "from": "savings",
- "to": "client",
- "v": 1
- },
- {
- "from": "savings",
- "to": "group",
- "v": 1
- },
- {
- "from": "savings",
- "to": "loan",
- "v": 1
- },
- {
- "from": "savings",
- "to": "organisation",
- "v": 1
- },
- {
- "from": "savings",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "accounting",
- "to": "account-transfers",
- "v": 1
- },
- {
- "from": "accounting",
- "to": "charge",
- "v": 1
- },
- {
- "from": "accounting",
- "to": "client",
- "v": 1
- },
- {
- "from": "accounting",
- "to": "loan",
- "v": 1
- },
- {
- "from": "accounting",
- "to": "organisation",
- "v": 1
- },
- {
- "from": "accounting",
- "to": "savings",
- "v": 1
- },
- {
- "from": "accounting",
- "to": "shareaccounts",
- "v": 1
- },
- {
- "from": "accounting",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "group",
- "to": "account-transfers",
- "v": 1
- },
- {
- "from": "group",
- "to": "calendar",
- "v": 1
- },
- {
- "from": "group",
- "to": "client",
- "v": 1
- },
- {
- "from": "group",
- "to": "loan",
- "v": 1
- },
- {
- "from": "group",
- "to": "organisation",
- "v": 1
- },
- {
- "from": "group",
- "to": "savings",
- "v": 1
- },
- {
- "from": "group",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "organisation",
- "to": "accounting",
- "v": 1
- },
- {
- "from": "organisation",
- "to": "calendar",
- "v": 1
- },
- {
- "from": "organisation",
- "to": "charge",
- "v": 1
- },
- {
- "from": "organisation",
- "to": "client",
- "v": 1
- },
- {
- "from": "organisation",
- "to": "loan",
- "v": 1
- },
- {
- "from": "organisation",
- "to": "savings",
- "v": 1
- },
- {
- "from": "organisation",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "shareaccounts",
- "to": "account-transfers",
- "v": 1
- },
- {
- "from": "shareaccounts",
- "to": "accounting",
- "v": 1
- },
- {
- "from": "shareaccounts",
- "to": "charge",
- "v": 1
- },
- {
- "from": "shareaccounts",
- "to": "client",
- "v": 1
- },
- {
- "from": "shareaccounts",
- "to": "organisation",
- "v": 1
- },
- {
- "from": "shareaccounts",
- "to": "savings",
- "v": 1
- },
- {
- "from": "shareaccounts",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "charge",
- "to": "accounting",
- "v": 1
- },
- {
- "from": "charge",
- "to": "loan",
- "v": 1
- },
- {
- "from": "charge",
- "to": "organisation",
- "v": 1
- },
- {
- "from": "charge",
- "to": "savings",
- "v": 1
- },
- {
- "from": "charge",
- "to": "shareaccounts",
- "v": 1
- },
- {
- "from": "charge",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "notifications",
- "to": "client",
- "v": 1
- },
- {
- "from": "notifications",
- "to": "loan",
- "v": 1
- },
- {
- "from": "notifications",
- "to": "organisation",
- "v": 1
- },
- {
- "from": "notifications",
- "to": "savings",
- "v": 1
- },
- {
- "from": "notifications",
- "to": "shareaccounts",
- "v": 1
- },
- {
- "from": "notifications",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "calendar",
- "to": "client",
- "v": 1
- },
- {
- "from": "calendar",
- "to": "group",
- "v": 1
- },
- {
- "from": "calendar",
- "to": "loan",
- "v": 1
- },
- {
- "from": "calendar",
- "to": "organisation",
- "v": 1
- },
- {
- "from": "calendar",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "batch",
- "to": "client",
- "v": 1
- },
- {
- "from": "batch",
- "to": "loan",
- "v": 1
- },
- {
- "from": "batch",
- "to": "savings",
- "v": 1
- },
- {
- "from": "batch",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "teller",
- "to": "accounting",
- "v": 1
- },
- {
- "from": "teller",
- "to": "client",
- "v": 1
- },
- {
- "from": "teller",
- "to": "organisation",
- "v": 1
- },
- {
- "from": "teller",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "rate",
- "to": "charge",
- "v": 1
- },
- {
- "from": "rate",
- "to": "loan",
- "v": 1
- },
- {
- "from": "rate",
- "to": "useradministration",
- "v": 1
- },
- {
- "from": "useradministration",
- "to": "client",
- "v": 1
- },
- {
- "from": "useradministration",
- "to": "organisation",
- "v": 1
- }
- ]
- },
- "mark": "rect",
- "encoding": {
- "y": {
- "field": "from",
- "type": "nominal",
- "title": "depends on ->"
- },
- "x": {
- "field": "to",
- "type": "nominal",
- "title": null,
- "axis": {
- "labelAngle": -45,
- "orient": "top"
- }
- },
- "color": {
- "value": "#d1495b"
- },
- "tooltip": [
- {
- "field": "from",
- "type": "nominal"
- },
- {
- "field": "to",
- "type": "nominal"
- }
- ]
- }
-}</pre>
+<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz4KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj48c3ZnIGNsYXNzPSdtYXJrcycgaGVpZ2h0PSc2MjUnIHByZXNlcnZlQXNwZWN0UmF0aW89J3hNaWRZTWlkIG1lZXQnIHZlcnNpb249JzEuMScgdmlld0JveD0nMCAwIDY4NyA2MjUnIHdpZHRoPSc2ODcnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPjxyZWN0IGZpbGw9
[...]
</div>
+<div class="title">Figure 6. Cross-feature dependencies — the row depends on
the column</div>
</div>
<div class="paragraph">
<p>Three readings are worth drawing out before the classification:</p>
@@ -48098,7 +47171,7 @@ A proposal to cover the read-side (query) is in
preparation.
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSc1NzdweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjEzOTFweDtoZWlnaHQ6NTc3cHg7YmFja2dyb3VuZDojRkZGRkZGOycgdmVyc2lvbj0nMS4xJyB2aWV3Qm94PScwIDAgMTM5MSA1NzcnIHdpZHRoPScxMzkxcHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48
[...]
</div>
-<div class="title">Figure 4. Command Processing Architecture Level 1 -
Core</div>
+<div class="title">Figure 7. Command Processing Architecture Level 1 -
Core</div>
</div>
</div>
</div>
@@ -48116,7 +47189,7 @@ A proposal to cover the read-side (query) is in
preparation.
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSc1NDBweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjk5M3B4O2hlaWdodDo1NDBweDtiYWNrZ3JvdW5kOiNGRkZGRkY7JyB2ZXJzaW9uPScxLjEnIHZpZXdCb3g9JzAgMCA5OTMgNTQwJyB3aWR0aD0nOTkzcHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48Zz48
[...]
</div>
-<div class="title">Figure 5. Command Processing Architecture Level 2 -
Dispatcher</div>
+<div class="title">Figure 8. Command Processing Architecture Level 2 -
Dispatcher</div>
</div>
<div class="listingblock">
<div class="title">Command Dispatcher</div>
@@ -48202,7 +47275,7 @@ Early phase of development. No prototype available yet,
but most likely module <
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PScyOTBweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjEyMTVweDtoZWlnaHQ6MjkwcHg7YmFja2dyb3VuZDojRkZGRkZGOycgdmVyc2lvbj0nMS4xJyB2aWV3Qm94PScwIDAgMTIxNSAyOTAnIHdpZHRoPScxMjE1cHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48
[...]
</div>
-<div class="title">Figure 6. Command Processing Architecture Level 2 -
Handlers</div>
+<div class="title">Figure 9. Command Processing Architecture Level 2 -
Handlers</div>
</div>
<div class="sect3">
<h4 id="_handler_manager"><a class="link" href="#_handler_manager">Handler
Manager</a></h4>
@@ -48258,7 +47331,7 @@ Early phase of development. No prototype available yet,
but most likely module <
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSc3MDhweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjEyODRweDtoZWlnaHQ6NzA4cHg7YmFja2dyb3VuZDojRkZGRkZGOycgdmVyc2lvbj0nMS4xJyB2aWV3Qm94PScwIDAgMTI4NCA3MDgnIHdpZHRoPScxMjg0cHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48
[...]
</div>
-<div class="title">Figure 7. Command Processing Architecture Level 2 -
Hooks</div>
+<div class="title">Figure 10. Command Processing Architecture Level 2 -
Hooks</div>
</div>
<div class="sect3">
<h4 id="_hook_manager"><a class="link" href="#_hook_manager">Hook
Manager</a></h4>
@@ -48342,7 +47415,7 @@ Early phase of development. No prototype available yet,
but most likely module <
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSc2MzJweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjkxMnB4O2hlaWdodDo2MzJweDtiYWNrZ3JvdW5kOiNGRkZGRkY7JyB2ZXJzaW9uPScxLjEnIHZpZXdCb3g9JzAgMCA5MTIgNjMyJyB3aWR0aD0nOTEycHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48Zz48
[...]
</div>
-<div class="title">Figure 8. Command Processing Architecture Level 2 -
Store</div>
+<div class="title">Figure 11. Command Processing Architecture Level 2 -
Store</div>
</div>
</div>
<div class="sect2">
@@ -60188,7 +59261,7 @@ a loan that is not charged off.</p>
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSczOTBweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjk4NXB4O2hlaWdodDozOTBweDtiYWNrZ3JvdW5kOiNGRkZGRkY7JyB2ZXJzaW9uPScxLjEnIHZpZXdCb3g9JzAgMCA5ODUgMzkwJyB3aWR0aD0nOTg1cHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48Zz48
[...]
</div>
-<div class="title">Figure 9. Charge-off request/response flow</div>
+<div class="title">Figure 12. Charge-off request/response flow</div>
</div>
</div>
</div>
@@ -61337,10 +60410,10 @@ which admit <code>CLOSED_WRITTEN_OFF</code>:</p>
<div class="ulist">
<ul>
<li>
-<p><code>WorkingCapitalLoanWrittenOffBusinessEvent</code> — emitted after a
write-off.</p>
+<p><code>WorkingCapitalLoanWriteOffTransactionBusinessEvent</code> — emitted
after a write-off.</p>
</li>
<li>
-<p><code>WorkingCapitalLoanUndoWrittenOffBusinessEvent</code> — emitted after
an undo write-off.</p>
+<p><code>WorkingCapitalLoanUndoWriteOffTransactionBusinessEvent</code> —
emitted after an undo write-off.</p>
</li>
<li>
<p><code>WorkingCapitalLoanBalanceChangedBusinessEvent</code> — emitted by
both operations, since the write-off zeroes the<br>
@@ -66157,7 +65230,7 @@ inserts the dedicated permission.</p>
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSczNTRweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjcwMXB4O2hlaWdodDozNTRweDtiYWNrZ3JvdW5kOiNGRkZGRkY7JyB2ZXJzaW9uPScxLjEnIHZpZXdCb3g9JzAgMCA3MDEgMzU0JyB3aWR0aD0nNzAxcHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48Zz48
[...]
</div>
-<div class="title">Figure 10. Entity-Relationship — action tables reused by
Disable / Enable</div>
+<div class="title">Figure 13. Entity-Relationship — action tables reused by
Disable / Enable</div>
</div>
<div class="sect4">
<h5 id="_m_wc_loan_delinquency_action"><a class="link"
href="#_m_wc_loan_delinquency_action"><code>m_wc_loan_delinquency_action</code></a></h5>
@@ -66704,7 +65777,7 @@ side effect (delinquency lifts the active
classification; breach only persists t
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSc3NTZweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjE0OThweDtoZWlnaHQ6NzU2cHg7YmFja2dyb3VuZDojRkZGRkZGOycgdmVyc2lvbj0nMS4xJyB2aWV3Qm94PScwIDAgMTQ5OCA3NTYnIHdpZHRoPScxNDk4cHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48
[...]
</div>
-<div class="title">Figure 11. Disable — request to persistence</div>
+<div class="title">Figure 14. Disable — request to persistence</div>
</div>
</div>
<div class="sect4">
@@ -66718,7 +65791,7 @@ recomputes its own schedule.</p>
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSc3MjFweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjE1MDRweDtoZWlnaHQ6NzIxcHg7YmFja2dyb3VuZDojRkZGRkZGOycgdmVyc2lvbj0nMS4xJyB2aWV3Qm94PScwIDAgMTUwNCA3MjEnIHdpZHRoPScxNTA0cHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48
[...]
</div>
-<div class="title">Figure 12. Enable — closing the window and recomputing</div>
+<div class="title">Figure 15. Enable — closing the window and recomputing</div>
</div>
</div>
<div class="sect4">
@@ -66730,7 +65803,7 @@ recomputes its own schedule.</p>
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PScxMzE5cHgnIHByZXNlcnZlQXNwZWN0UmF0aW89J25vbmUnIHN0eWxlPSd3aWR0aDo5MzVweDtoZWlnaHQ6MTMxOXB4O2JhY2tncm91bmQ6I0ZGRkZGRjsnIHZlcnNpb249JzEuMScgdmlld0JveD0nMCAwIDkzNSAxMzE5JyB3aWR0aD0nOTM1cHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48
[...]
</div>
-<div class="title">Figure 13. Disable / Enable validation gate</div>
+<div class="title">Figure 16. Disable / Enable validation gate</div>
</div>
</div>
</div>
@@ -75576,19 +74649,19 @@ Have a look at the specifications in
<code>fineract-provider</code> for an initi
<p><strong>Location</strong>:
<code>integration-tests/src/test/java/org/apache/fineract/integrationtests</code></p>
</li>
<li>
-<p><strong>Framework</strong>: JUnit 5 with REST Assured for HTTP
communication</p>
+<p><strong>Framework</strong>: JUnit 5. Tests under <code>client/feign</code>
drive the server through the generated Feign client; the remainder still use
REST Assured for HTTP communication</p>
</li>
<li>
-<p><strong>Base Class</strong>: Most tests extend
<code>BaseLoanIntegrationTest</code> or <code>IntegrationTest</code></p>
-</li>
-<li>
-<p><strong>Client Library</strong>: Uses Fineract client library for type-safe
API interactions</p>
+<p><strong>Base Class</strong>: Most tests extend
<code>BaseLoanIntegrationTest</code> or <code>IntegrationTest</code>; Feign
tests extend <code>FeignLoanTestBase</code> or
<code>FeignSavingsTestBase</code></p>
</li>
<li>
<p><strong>Prerequisites</strong>: Running Apache Fineract instance (default:
<a href="https://localhost:8443" class="bare">localhost:8443</a>)</p>
</li>
</ul>
</div>
+<div class="paragraph">
+<p>New tests should be written against the generated Feign client, which is
covered in <a href="#_feign_integration_tests">Feign Integration Tests</a>. The
rest of this chapter describes the setup shared by both, and the REST Assured
pattern used by the tests that have not yet been migrated.</p>
+</div>
</div>
<div class="sect4">
<h5 id="_key_characteristics_3"><a class="link"
href="#_key_characteristics_3">Key Characteristics</a></h5>
@@ -75629,18 +74702,32 @@ Have a look at the specifications in
<code>fineract-provider</code> for an initi
<div class="paragraph">
<p>Before running integration tests, ensure the databases are created:</p>
</div>
+<div class="paragraph">
+<p>There is a separate task per database engine: <code>createPGDB</code> for
PostgreSQL, <code>createDB</code> for MariaDB, and <code>createMySQLDB</code>
for MySQL.</p>
+</div>
<div class="listingblock">
<div class="content">
-<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Create
required databases</span>
-./gradlew createDB <span class="nt">-PdbName</span><span
class="o">=</span>fineract_tenants
-./gradlew createDB <span class="nt">-PdbName</span><span
class="o">=</span>fineract_default</code></pre>
+<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Create
required databases (PostgreSQL)</span>
+./gradlew createPGDB <span class="nt">-PdbName</span><span
class="o">=</span>fineract_tenants
+./gradlew createPGDB <span class="nt">-PdbName</span><span
class="o">=</span>fineract_default</code></pre>
</div>
</div>
</div>
<div class="sect4">
<h5 id="_fineract_instance"><a class="link"
href="#_fineract_instance">Fineract Instance</a></h5>
<div class="paragraph">
-<p>Integration tests run fineract instance from cargo plugin by default.</p>
+<p>Integration tests run fineract instance from cargo plugin by default. The
<code>test</code> task starts a Tomcat container, deploys
<code>fineract-provider.war</code> into it, waits for the instance to come up,
and stops it afterwards.</p>
+</div>
+<div class="paragraph">
+<p>Pass <code>-PcargoDisabled</code> to skip all of that and run against an
instance you started yourself:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="bash">./gradlew
:integration-tests:test <span class="nt">-PcargoDisabled</span> <span
class="nt">--tests</span> ClientLoanIntegrationTest</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Without it, the suite runs against the Cargo-managed container regardless
of any instance already listening on the configured port.</p>
</div>
</div>
</div>
@@ -75770,27 +74857,25 @@ curl <span class="nt">-k</span>
https://localhost:8443/actuator/health</code></p
<h5 id="_advanced_test_execution"><a class="link"
href="#_advanced_test_execution">Advanced Test Execution</a></h5>
<div class="sect5">
<h6 id="_run_with_test_filtering"><a class="link"
href="#_run_with_test_filtering">Run with Test Filtering</a></h6>
+<div class="paragraph">
+<p><code>--tests</code> is the only filter the <code>test</code> task accepts.
Repeat it to select several patterns; there is no corresponding exclude
option.</p>
+</div>
<div class="listingblock">
<div class="content">
-<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Run
tests excluding specific packages</span>
-./gradlew :integration-tests:test <span class="nt">--tests</span> <span
class="s2">"*"</span> <span class="se">\</span>
- <span class="nt">--exclude</span> <span class="s2">"*Deprecated*"</span>
-
-<span class="c"># Run only fast tests (custom tag)</span>
-./gradlew :integration-tests:test <span class="nt">--tests</span> <span
class="s2">"*Fast*"</span></code></pre>
+<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Select
several patterns</span>
+./gradlew :integration-tests:test <span class="se">\</span>
+ <span class="nt">--tests</span> <span class="s2">"*Loan*"</span> <span
class="se">\</span>
+ <span class="nt">--tests</span> <span
class="s2">"*Savings*"</span></code></pre>
</div>
</div>
+<div class="paragraph">
+<p>To skip a test permanently, annotate it with <code>@Disabled</code> rather
than filtering it on the command line.</p>
+</div>
</div>
<div class="sect5">
<h6 id="_parallel_execution"><a class="link"
href="#_parallel_execution">Parallel Execution</a></h6>
-<div class="listingblock">
-<div class="content">
-<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Run
tests in parallel</span>
-./gradlew :integration-tests:test <span class="nt">--parallel</span> <span
class="nt">--max-workers</span><span class="o">=</span>4
-
-<span class="c"># Set custom thread count</span>
-./gradlew :integration-tests:test <span class="nt">--parallel</span> <span
class="nt">--max-workers</span><span class="o">=</span>8</code></pre>
-</div>
+<div class="paragraph">
+<p><code>--max-workers</code> limits Gradle’s own worker count; it does
not fork the test JVM. Within-task parallelism is controlled by
<code>maxParallelForks</code> on the <code>test</code> task, which
<code>integration-tests</code> does not set — the suite runs in a single
JVM.</p>
</div>
<div class="admonitionblock warning">
<table>
@@ -75799,7 +74884,7 @@ curl <span class="nt">-k</span>
https://localhost:8443/actuator/health</code></p
<i class="fa icon-warning" title="Warning"></i>
</td>
<td class="content">
-Some integration tests may have dependencies on shared state. Use parallel
execution carefully and ensure tests are properly isolated.
+Integration tests share the database of the instance under test. Raising
<code>maxParallelForks</code> requires the tests to be isolated from one
another first.
</td>
</tr>
</table>
@@ -75809,13 +74894,25 @@ Some integration tests may have dependencies on
shared state. Use parallel execu
<h6 id="_run_with_custom_jvm_arguments"><a class="link"
href="#_run_with_custom_jvm_arguments">Run with Custom JVM Arguments</a></h6>
<div class="listingblock">
<div class="content">
-<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Increase
heap size for large test suites</span>
-./gradlew :integration-tests:test <span class="nt">-Xmx4g</span>
-
-<span class="c"># Enable debugging</span>
+<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Attach a
debugger to the test JVM (suspends until the debugger connects)</span>
./gradlew :integration-tests:test <span
class="nt">--debug-jvm</span></code></pre>
</div>
</div>
+<div class="paragraph">
+<p>The Gradle daemon’s own heap is set by
<code>org.gradle.jvmargs</code> in <code>gradle.properties</code>.</p>
+</div>
+<div class="admonitionblock caution">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-caution" title="Caution"></i>
+</td>
+<td class="content">
+Overriding it on the command line with <code>-Dorg.gradle.jvmargs</code>
replaces the whole value, including the <code>--add-exports</code> and
<code>--add-opens</code> flags the build needs. Repeat those flags if you
override it.
+</td>
+</tr>
+</table>
+</div>
</div>
<div class="sect5">
<h6 id="_generate_test_reports"><a class="link"
href="#_generate_test_reports">Generate Test Reports</a></h6>
@@ -75894,19 +74991,21 @@ Some integration tests may have dependencies on
shared state. Use parallel execu
<div class="sect3">
<h4 id="_test_structure"><a class="link" href="#_test_structure">Test
Structure</a></h4>
<div class="sect4">
-<h5 id="_baseloanintegrationtest_overview"><a class="link"
href="#_baseloanintegrationtest_overview">BaseLoanIntegrationTest
Overview</a></h5>
+<h5 id="_loan_test_base_class_overview"><a class="link"
href="#_loan_test_base_class_overview">Loan Test Base Class Overview</a></h5>
<div class="paragraph">
-<p><code>BaseLoanIntegrationTest</code> is the comprehensive base test class
for loan-related integration tests. It provides:</p>
+<p><code>FeignLoanTestBase</code> is the base class for loan-related
integration tests, and provides the helpers described below.
<code>BaseLoanIntegrationTest</code> offers the same surface for the REST
Assured tests that have not yet been migrated.</p>
</div>
<div class="sect5">
<h6 id="_pre_configured_loan_product_creation"><a class="link"
href="#_pre_configured_loan_product_creation">Pre-configured Loan Product
Creation</a></h6>
+<div class="paragraph">
+<p>These methods return a <code>PostLoanProductsRequest</code> describing the
product. Pass it to <code>createLoanProduct</code> to create the product and
obtain its identifier.</p>
+</div>
<div class="listingblock">
<div class="content">
-<pre class="rouge highlight"><code data-lang="java"><span class="c1">// Create
standard loan products</span>
-<span
class="n">createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct</span><span
class="o">()</span>
-<span class="n">create4IProgressive</span><span class="o">()</span> <span
class="c1">// Progressive loan products</span>
-<span class="n">create4IProgressiveWithCapitalizedIncome</span><span
class="o">()</span> <span class="c1">// With capitalized income</span>
-<span
class="n">createOnePeriod30DaysPeriodicAccrualProductWithAdvancedPaymentAllocation</span><span
class="o">()</span></code></pre>
+<pre class="rouge highlight"><code data-lang="java"><span
class="n">createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct</span><span
class="o">()</span>
+<span class="n">create4IProgressive</span><span class="o">()</span>
+<span class="n">create4IProgressiveWithCapitalizedIncome</span><span
class="o">()</span>
+<span
class="n">createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation</span><span
class="o">()</span></code></pre>
</div>
</div>
</div>
@@ -75994,6 +75093,12 @@ Some integration tests may have dependencies on shared
state. Use parallel execu
<h5 id="_common_test_patterns"><a class="link"
href="#_common_test_patterns">Common Test Patterns</a></h5>
<div class="sect5">
<h6 id="_test_setup_pattern"><a class="link" href="#_test_setup_pattern">Test
Setup Pattern</a></h6>
+<div class="paragraph">
+<p>A test extending <code>FeignLoanTestBase</code> needs no setup block; the
base class builds the client and its helpers.</p>
+</div>
+<div class="paragraph">
+<p>The REST Assured tests that have not yet been migrated build a request
specification per test:</p>
+</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="java"><span
class="nd">@BeforeEach</span>
@@ -76010,6 +75115,9 @@ Some integration tests may have dependencies on shared
state. Use parallel execu
<span class="o">}</span></code></pre>
</div>
</div>
+<div class="paragraph">
+<p>Do not copy this into a new test.
<code>loginIntoServerAndGetBase64EncodedAuthenticationKey</code> authenticates
against the server on every test method.</p>
+</div>
</div>
<div class="sect5">
<h6 id="_date_specific_operations"><a class="link"
href="#_date_specific_operations">Date-Specific Operations</a></h6>
@@ -76058,19 +75166,15 @@ Some integration tests may have dependencies on
shared state. Use parallel execu
</div>
<div class="sect5">
<h6 id="_progressive_loan_testing"><a class="link"
href="#_progressive_loan_testing">Progressive Loan Testing</a></h6>
+<div class="paragraph">
+<p>The <code>create*</code> methods return a
<code>PostLoanProductsRequest</code> describing the product, not a product
identifier. Pass the request to <code>createLoanProduct</code> to obtain the
identifier, customising it first if the test needs to.</p>
+</div>
<div class="listingblock">
<div class="content">
-<pre class="rouge highlight"><code data-lang="java"><span class="c1">// Create
progressive loan product</span>
-<span class="nc">Long</span> <span class="n">productId</span> <span
class="o">=</span> <span class="n">create4IProgressive</span><span
class="o">();</span>
-
-<span class="c1">// Apply and approve</span>
-<span class="nc">Long</span> <span class="n">loanId</span> <span
class="o">=</span> <span class="n">applyAndApproveProgressiveLoan</span><span
class="o">(</span><span class="n">clientId</span><span class="o">,</span> <span
class="n">productId</span><span class="o">,</span>
- <span class="n">amount</span><span class="o">,</span> <span
class="n">numberOfRepayments</span><span class="o">,</span> <span
class="n">interestRate</span><span class="o">);</span>
+<pre class="rouge highlight"><code data-lang="java"><span
class="nc">Long</span> <span class="n">productId</span> <span
class="o">=</span> <span class="n">createLoanProduct</span><span
class="o">(</span><span class="n">create4IProgressive</span><span
class="o">());</span>
-<span class="c1">// Test advanced features</span>
-<span class="n">testCapitalizedIncome</span><span class="o">(</span><span
class="n">loanId</span><span class="o">);</span>
-<span class="n">testDownPayment</span><span class="o">(</span><span
class="n">loanId</span><span class="o">);</span>
-<span class="n">testAdvancedPaymentAllocation</span><span
class="o">(</span><span class="n">loanId</span><span
class="o">);</span></code></pre>
+<span class="nc">Long</span> <span class="n">loanId</span> <span
class="o">=</span> <span class="n">applyAndApproveProgressiveLoan</span><span
class="o">(</span><span class="n">clientId</span><span class="o">,</span> <span
class="n">productId</span><span class="o">,</span> <span class="s">"01 January
2024"</span><span class="o">,</span>
+ <span class="mf">1000.0</span><span class="o">,</span> <span
class="mf">10.0</span><span class="o">,</span> <span class="mi">12</span><span
class="o">,</span> <span class="n">request</span> <span class="o">-></span>
<span class="n">request</span><span class="o">.</span><span
class="na">setLoanTermFrequency</span><span class="o">(</span><span
class="mi">12</span><span class="o">));</span></code></pre>
</div>
</div>
</div>
@@ -76083,30 +75187,16 @@ Some integration tests may have dependencies on
shared state. Use parallel execu
<div class="sect5">
<h6 id="_1_create_test_class"><a class="link" href="#_1_create_test_class">1.
Create Test Class</a></h6>
<div class="paragraph">
-<p>Create a new test class in
<code>integration-tests/src/test/java/org/apache/fineract/integrationtests/</code>:</p>
+<p>Create a new test class in
<code>integration-tests/src/test/java/org/apache/fineract/integrationtests/</code>
and extend <code>FeignLoanTestBase</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="java"><span
class="kn">package</span> <span
class="nn">org.apache.fineract.integrationtests</span><span class="o">;</span>
-<span class="kn">import</span> <span
class="nn">org.apache.fineract.integrationtests.common.Utils</span><span
class="o">;</span>
+<span class="kn">import</span> <span
class="nn">org.apache.fineract.integrationtests.client.feign.FeignLoanTestBase</span><span
class="o">;</span>
<span class="kn">import</span> <span
class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>
-<span class="kn">import</span> <span
class="nn">org.junit.jupiter.api.BeforeEach</span><span class="o">;</span>
-<span class="kd">public</span> <span class="kd">class</span> <span
class="nc">MyNewFeatureIntegrationTest</span> <span class="kd">extends</span>
<span class="nc">BaseLoanIntegrationTest</span> <span class="o">{</span>
-
- <span class="nd">@BeforeEach</span>
- <span class="kd">public</span> <span class="kt">void</span> <span
class="nf">setup</span><span class="o">()</span> <span class="o">{</span>
- <span class="nc">Utils</span><span class="o">.</span><span
class="na">initializeRESTAssured</span><span class="o">();</span>
- <span class="k">this</span><span class="o">.</span><span
class="na">requestSpec</span> <span class="o">=</span> <span
class="k">new</span> <span class="nc">RequestSpecBuilder</span><span
class="o">()</span>
- <span class="o">.</span><span
class="na">setContentType</span><span class="o">(</span><span
class="nc">ContentType</span><span class="o">.</span><span
class="na">JSON</span><span class="o">)</span>
- <span class="o">.</span><span class="na">build</span><span
class="o">();</span>
- <span class="k">this</span><span class="o">.</span><span
class="na">requestSpec</span><span class="o">.</span><span
class="na">header</span><span class="o">(</span><span
class="s">"Authorization"</span><span class="o">,</span> <span class="s">"Basic
"</span> <span class="o">+</span>
- <span class="nc">Utils</span><span class="o">.</span><span
class="na">loginIntoServerAndGetBase64EncodedAuthenticationKey</span><span
class="o">());</span>
- <span class="k">this</span><span class="o">.</span><span
class="na">responseSpec</span> <span class="o">=</span> <span
class="k">new</span> <span class="nc">ResponseSpecBuilder</span><span
class="o">()</span>
- <span class="o">.</span><span
class="na">expectStatusCode</span><span class="o">(</span><span
class="mi">200</span><span class="o">)</span>
- <span class="o">.</span><span class="na">build</span><span
class="o">();</span>
- <span class="o">}</span>
+<span class="kd">public</span> <span class="kd">class</span> <span
class="nc">MyNewFeatureIntegrationTest</span> <span class="kd">extends</span>
<span class="nc">FeignLoanTestBase</span> <span class="o">{</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span
class="nf">testMyNewFeature</span><span class="o">()</span> <span
class="o">{</span>
@@ -76115,33 +75205,32 @@ Some integration tests may have dependencies on
shared state. Use parallel execu
<span class="o">}</span></code></pre>
</div>
</div>
+<div class="paragraph">
+<p>The conventions that apply to the body of such a test — how to call the
API, how to assert failures, and what to do when a generated model is missing a
field — are covered in <a href="#_feign_integration_tests">Feign Integration
Tests</a>.</p>
+</div>
</div>
<div class="sect5">
<h6 id="_2_use_helper_classes"><a class="link"
href="#_2_use_helper_classes">2. Use Helper Classes</a></h6>
<div class="paragraph">
-<p>Leverage existing helper classes:</p>
+<p><code>FeignLoanTestBase</code> exposes the helpers as fields, already
constructed against the shared client. There is no request specification to
pass around:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="rouge highlight"><code data-lang="java"><span class="c1">// Client
operations</span>
-<span class="nc">ClientHelper</span> <span class="n">clientHelper</span> <span
class="o">=</span> <span class="k">new</span> <span
class="nc">ClientHelper</span><span class="o">(</span><span
class="n">requestSpec</span><span class="o">,</span> <span
class="n">responseSpec</span><span class="o">);</span>
-<span class="nc">Long</span> <span class="n">clientId</span> <span
class="o">=</span> <span class="n">clientHelper</span><span
class="o">.</span><span class="na">createClient</span><span
class="o">(...);</span>
-
-<span class="c1">// Loan operations</span>
-<span class="nc">LoanTransactionHelper</span> <span
class="n">loanHelper</span> <span class="o">=</span> <span class="k">new</span>
<span class="nc">LoanTransactionHelper</span><span class="o">(</span><span
class="n">requestSpec</span><span class="o">,</span> <span
class="n">responseSpec</span><span class="o">);</span>
-<span class="nc">Long</span> <span class="n">loanId</span> <span
class="o">=</span> <span class="n">loanHelper</span><span
class="o">.</span><span class="na">applyLoan</span><span class="o">(...);</span>
-
-<span class="c1">// Account operations</span>
-<span class="nc">AccountHelper</span> <span class="n">accountHelper</span>
<span class="o">=</span> <span class="k">new</span> <span
class="nc">AccountHelper</span><span class="o">(</span><span
class="n">requestSpec</span><span class="o">,</span> <span
class="n">responseSpec</span><span class="o">);</span>
-
-<span class="c1">// Business date operations</span>
-<span class="nc">BusinessDateHelper</span> <span
class="n">businessDateHelper</span> <span class="o">=</span> <span
class="k">new</span> <span class="nc">BusinessDateHelper</span><span
class="o">();</span>
-<span class="n">businessDateHelper</span><span class="o">.</span><span
class="na">updateBusinessDate</span><span class="o">(...);</span>
-
-<span class="c1">// COB operations</span>
-<span class="nc">InlineLoanCOBHelper</span> <span class="n">cobHelper</span>
<span class="o">=</span> <span class="k">new</span> <span
class="nc">InlineLoanCOBHelper</span><span class="o">(</span><span
class="n">requestSpec</span><span class="o">,</span> <span
class="n">responseSpec</span><span class="o">);</span>
-<span class="n">cobHelper</span><span class="o">.</span><span
class="na">executeInlineCOB</span><span class="o">(</span><span
class="n">loanId</span><span class="o">);</span></code></pre>
+<pre class="rouge highlight"><code data-lang="java"><span
class="nc">Long</span> <span class="n">clientId</span> <span class="o">=</span>
<span class="n">clientHelper</span><span class="o">.</span><span
class="na">createClient</span><span class="o">(...);</span>
+<span class="nc">PostLoansResponse</span> <span class="n">loan</span> <span
class="o">=</span> <span class="n">loanHelper</span><span
class="o">.</span><span class="na">applyForLoan</span><span
class="o">(</span><span class="n">request</span><span class="o">);</span>
+<span class="n">businessDateHelper</span><span class="o">.</span><span
class="na">updateBusinessDate</span><span class="o">(...);</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Helpers that the base class does not expose are constructed with the
client:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span
class="nc">FeignOfficeHelper</span> <span class="n">officeHelper</span> <span
class="o">=</span> <span class="k">new</span> <span
class="nc">FeignOfficeHelper</span><span class="o">(</span><span
class="nc">FineractFeignClientHelper</span><span class="o">.</span><span
class="na">getFineractFeignClient</span><span class="o">());</span></code></pre>
+</div>
</div>
+<div class="paragraph">
+<p>The full set lives in
<code>integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers</code>.</p>
</div>
</div>
<div class="sect5">
@@ -76254,12 +75343,23 @@ netstat <span class="nt">-tulpn</span> | <span
class="nb">grep </span>8443
<span class="c"># Check Fineract logs (logs go to console/stdout)</span>
<span class="c"># If running in background with output redirection:</span>
-<span class="c"># tail -f build/bootRun.log</span>
-
-<span class="c"># Restart Fineract if needed</span>
-pkill <span class="nt">-f</span> bootRun
-./gradlew bootRun &</code></pre>
+<span class="c"># tail -f build/bootRun.log</span></code></pre>
+</div>
+</div>
+<div class="admonitionblock caution">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-caution" title="Caution"></i>
+</td>
+<td class="content">
+Do not stop the instance with <code>pkill -f bootRun</code> or by
pattern-matching Gradle daemons. The server is a child of the daemon that
launched it, so those patterns take the daemon down with it. Stop it from the
terminal running it.
+</td>
+</tr>
+</table>
</div>
+<div class="paragraph">
+<p>Rebuilding while an instance is running replaces the classes it loaded from
<code>build/</code>. Reads may continue to work while writes start failing.
Build first, then start the server.</p>
</div>
</div>
<div class="sect5">
@@ -76272,21 +75372,20 @@ pkill <span class="nt">-f</span> bootRun
</div>
<div class="listingblock">
<div class="content">
-<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Check
default credentials</span>
-mysql <span class="nt">-u</span> root <span class="nt">-pmysql</span>
fineract_default <span class="nt">-e</span> <span class="se">\</span>
- <span class="s2">"SELECT username, password FROM m_appuser WHERE username =
'mifos';"</span>
-
-<span class="c"># Reset credentials if needed</span>
-mysql <span class="nt">-u</span> root <span class="nt">-pmysql</span>
fineract_default <span class="nt">-e</span> <span class="se">\</span>
- <span class="s2">"UPDATE m_appuser SET password =
'5jdQ3dNQXHPzCuBbZVdQZ2XnVlPc3l2l' </span><span class="se">\</span><span
class="s2">
- WHERE username = 'mifos';"</span>
+<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Check
the user exists and is not locked</span>
+psql <span class="nt">-U</span> root <span class="nt">-d</span>
fineract_default <span class="se">\</span>
+ <span class="nt">-c</span> <span class="s2">"SELECT username, nonlocked FROM
m_appuser WHERE username = 'mifos';"</span>
<span class="c"># Verify connection settings</span>
<span class="nb">echo</span> <span class="s2">"Protocol: </span><span
class="k">${</span><span class="nv">BACKEND_PROTOCOL</span><span
class="k">:-</span><span class="nv">https</span><span class="k">}</span><span
class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"Host: </span><span
class="k">${</span><span class="nv">BACKEND_HOST</span><span
class="k">:-</span><span class="nv">localhost</span><span
class="k">}</span><span class="s2">"</span>
-<span class="nb">echo</span> <span class="s2">"Port: </span><span
class="k">${</span><span class="nv">BACKEND_PORT</span><span
class="k">:-</span><span class="nv">8443</span><span class="k">}</span><span
class="s2">"</span></code></pre>
+<span class="nb">echo</span> <span class="s2">"Port: </span><span
class="k">${</span><span class="nv">BACKEND_PORT</span><span
class="k">:-</span><span class="nv">8443</span><span class="k">}</span><span
class="s2">"</span>
+<span class="nb">echo</span> <span class="s2">"Tenant: </span><span
class="k">${</span><span class="nv">BACKEND_TENANT</span><span
class="k">:-</span><span class="nv">default</span><span class="k">}</span><span
class="s2">"</span></code></pre>
</div>
</div>
+<div class="paragraph">
+<p>Check <code>BACKEND_TENANT</code> as well as the credentials. The tenant
identifier is resolved before the user is authenticated, so an unknown tenant
fails every test in the same way a bad password does.</p>
+</div>
</div>
<div class="sect5">
<h6 id="_data_inconsistency"><a class="link" href="#_data_inconsistency">Data
Inconsistency</a></h6>
@@ -76298,19 +75397,27 @@ mysql <span class="nt">-u</span> root <span
class="nt">-pmysql</span> fineract_d
</div>
<div class="listingblock">
<div class="content">
-<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Reset
database</span>
-mysql <span class="nt">-u</span> root <span class="nt">-pmysql</span> <span
class="nt">-e</span> <span class="s2">"DROP DATABASE fineract_default;"</span>
-mysql <span class="nt">-u</span> root <span class="nt">-pmysql</span> <span
class="nt">-e</span> <span class="s2">"DROP DATABASE fineract_tenants;"</span>
+<pre class="rouge highlight"><code data-lang="bash"><span class="c"># 1. Stop
the running instance first: PostgreSQL refuses to drop a database</span>
+<span class="c"># while the server still holds connections to it. Stop it
from the</span>
+<span class="c"># terminal it is running in.</span>
-<span class="c"># Recreate databases</span>
-./gradlew createDB <span class="nt">-PdbName</span><span
class="o">=</span>fineract_tenants
-./gradlew createDB <span class="nt">-PdbName</span><span
class="o">=</span>fineract_default
+<span class="c"># 2. Reset the databases</span>
+psql <span class="nt">-U</span> root <span class="nt">-d</span> postgres <span
class="nt">-c</span> <span class="s2">"DROP DATABASE fineract_default;"</span>
+psql <span class="nt">-U</span> root <span class="nt">-d</span> postgres <span
class="nt">-c</span> <span class="s2">"DROP DATABASE fineract_tenants;"</span>
+
+./gradlew createPGDB <span class="nt">-PdbName</span><span
class="o">=</span>fineract_tenants
+./gradlew createPGDB <span class="nt">-PdbName</span><span
class="o">=</span>fineract_default
-<span class="c"># Restart Fineract</span>
-pkill <span class="nt">-f</span> bootRun
-./gradlew bootRun &</code></pre>
+<span class="c"># 3. Start Fineract again</span>
+./gradlew bootRun</code></pre>
</div>
</div>
+<div class="paragraph">
+<p><code>createPGDB</code> is the PostgreSQL task. <code>createDB</code>
targets MariaDB and <code>createMySQLDB</code> targets MySQL; using the wrong
one leaves the instance without the databases it expects.</p>
+</div>
+<div class="paragraph">
+<p>A long-lived development database is itself a source of failures. Tests
that generate short random names begin to collide once the table holds enough
rows, and tests that page through a listing stop finding what they created. A
collision clears on a re-run; a paging miss does not. Reset the databases
rather than re-running repeatedly.</p>
+</div>
</div>
<div class="sect5">
<h6 id="_test_timeout"><a class="link" href="#_test_timeout">Test
Timeout</a></h6>
@@ -76318,20 +75425,24 @@ pkill <span class="nt">-f</span> bootRun
<p><strong>Symptom</strong>: Tests hang or timeout</p>
</div>
<div class="paragraph">
+<p>Timeouts are declared per test with JUnit’s <code>@Timeout</code>, or
by the awaiting helper a test uses; there is no global timeout property.</p>
+</div>
+<div class="paragraph">
<p><strong>Solutions</strong>:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Increase
test timeout</span>
-./gradlew :integration-tests:test <span class="nt">-Dtest</span>.timeout<span
class="o">=</span>600
-
-<span class="c"># Check for database locks</span>
-mysql <span class="nt">-u</span> root <span class="nt">-pmysql</span>
fineract_default <span class="nt">-e</span> <span class="s2">"SHOW
PROCESSLIST;"</span>
+<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Check
for blocked queries</span>
+psql <span class="nt">-U</span> root <span class="nt">-d</span>
fineract_default <span class="se">\</span>
+ <span class="nt">-c</span> <span class="s2">"SELECT pid, state, wait_event,
query FROM pg_stat_activity WHERE state <> 'idle';"</span>
-<span class="c"># Kill long-running queries</span>
-mysql <span class="nt">-u</span> root <span class="nt">-pmysql</span>
fineract_default <span class="nt">-e</span> <span class="s2">"KILL
<process_id>;"</span></code></pre>
+<span class="c"># Cancel a long-running query</span>
+psql <span class="nt">-U</span> root <span class="nt">-d</span>
fineract_default <span class="nt">-c</span> <span class="s2">"SELECT
pg_cancel_backend(<pid>);"</span></code></pre>
</div>
</div>
+<div class="paragraph">
+<p>A test that waits on a scheduled job can exceed its budget simply because
the job is slow on a database with accumulated data, rather than because of a
regression. Re-run the class on its own before treating it as a failure.</p>
+</div>
</div>
<div class="sect5">
<h6 id="_memory_issues"><a class="link" href="#_memory_issues">Memory
Issues</a></h6>
@@ -76341,17 +75452,30 @@ mysql <span class="nt">-u</span> root <span
class="nt">-pmysql</span> fineract_d
<div class="paragraph">
<p><strong>Solutions</strong>:</p>
</div>
+<div class="paragraph">
+<p>The <code>test</code> task always forks its own JVM, and its heap comes
from <code>maxHeapSize</code> on that task rather than from
<code>org.gradle.jvmargs</code>. <code>integration-tests</code> does not set
it, so the fork runs at Gradle’s default. Raise it in
<code>integration-tests/build.gradle</code>:</p>
+</div>
<div class="listingblock">
<div class="content">
-<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Increase
heap size</span>
-./gradlew :integration-tests:test <span class="nt">-Xmx4g</span> <span
class="nt">-Xms2g</span>
-
-<span class="c"># Run fewer tests in parallel</span>
-./gradlew :integration-tests:test <span class="nt">--max-workers</span><span
class="o">=</span>2
-
-<span class="c"># Clean build directory</span>
-./gradlew clean</code></pre>
+<pre class="rouge highlight"><code data-lang="groovy"><span
class="n">tasks</span><span class="o">.</span><span
class="na">named</span><span class="o">(</span><span
class="s1">'test'</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">maxHeapSize</span> <span class="o">=</span> <span
class="s1">'2g'</span>
+<span class="o">}</span></code></pre>
+</div>
</div>
+<div class="paragraph">
+<p><code>org.gradle.jvmargs</code> in <code>gradle.properties</code> sizes the
Gradle daemon, which is a separate process. Raise that only for an
out-of-memory failure in the build itself, such as during client generation.</p>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+"Gradle build daemon disappeared unexpectedly" is not the same failure. It is
usually the operating system’s out-of-memory killer, meaning
<code>org.gradle.jvmargs</code> asks for more heap than the machine has; lower
it.
+</td>
+</tr>
+</table>
</div>
</div>
</div>
@@ -76402,22 +75526,22 @@ mysql <span class="nt">-u</span> root <span
class="nt">-pmysql</span> fineract_d
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="bash"><span class="c"># Check
loan status</span>
-mysql <span class="nt">-u</span> root <span class="nt">-pmysql</span>
fineract_default <span class="nt">-e</span> <span class="se">\</span>
+psql <span class="nt">-U</span> root <span class="nt">-d</span>
fineract_default <span class="nt">-c</span> <span class="se">\</span>
<span class="s2">"SELECT id, account_no, loan_status_id, principal_amount
</span><span class="se">\</span><span class="s2">
FROM m_loan ORDER BY id DESC LIMIT 10;"</span>
<span class="c"># Check transactions</span>
-mysql <span class="nt">-u</span> root <span class="nt">-pmysql</span>
fineract_default <span class="nt">-e</span> <span class="se">\</span>
+psql <span class="nt">-U</span> root <span class="nt">-d</span>
fineract_default <span class="nt">-c</span> <span class="se">\</span>
<span class="s2">"SELECT loan_id, transaction_type_enum, amount,
transaction_date </span><span class="se">\</span><span class="s2">
FROM m_loan_transaction WHERE loan_id = <loan_id>;"</span>
<span class="c"># Check journal entries</span>
-mysql <span class="nt">-u</span> root <span class="nt">-pmysql</span>
fineract_default <span class="nt">-e</span> <span class="se">\</span>
+psql <span class="nt">-U</span> root <span class="nt">-d</span>
fineract_default <span class="nt">-c</span> <span class="se">\</span>
<span class="s2">"SELECT entry_date, account_id, type_enum, amount
</span><span class="se">\</span><span class="s2">
FROM acc_gl_journal_entry WHERE loan_id = <loan_id>;"</span>
<span class="c"># Check configurations</span>
-mysql <span class="nt">-u</span> root <span class="nt">-pmysql</span>
fineract_default <span class="nt">-e</span> <span class="se">\</span>
+psql <span class="nt">-U</span> root <span class="nt">-d</span>
fineract_default <span class="nt">-c</span> <span class="se">\</span>
<span class="s2">"SELECT name, enabled FROM c_configuration </span><span
class="se">\</span><span class="s2">
WHERE name LIKE '%business%' OR name LIKE '%enable%';"</span></code></pre>
</div>
@@ -76426,16 +75550,21 @@ mysql <span class="nt">-u</span> root <span
class="nt">-pmysql</span> fineract_d
<div class="sect5">
<h6 id="_api_response_debugging"><a class="link"
href="#_api_response_debugging">API Response Debugging</a></h6>
<div class="paragraph">
-<p>Add logging to test methods:</p>
+<p>A failed Feign call raises <code>CallFailedRuntimeException</code>, which
carries the status, the developer message and the raw response body:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="rouge highlight"><code data-lang="java"><span
class="nc">Response</span> <span class="n">response</span> <span
class="o">=</span> <span class="n">loanHelper</span><span
class="o">.</span><span class="na">applyLoan</span><span class="o">(...);</span>
-<span class="nc">System</span><span class="o">.</span><span
class="na">out</span><span class="o">.</span><span
class="na">println</span><span class="o">(</span><span class="s">"Response:
"</span> <span class="o">+</span> <span class="n">response</span><span
class="o">.</span><span class="na">asString</span><span class="o">());</span>
-<span class="nc">System</span><span class="o">.</span><span
class="na">out</span><span class="o">.</span><span
class="na">println</span><span class="o">(</span><span class="s">"Status Code:
"</span> <span class="o">+</span> <span class="n">response</span><span
class="o">.</span><span class="na">getStatusCode</span><span
class="o">());</span>
-
-<span class="c1">// Or use logger</span>
-<span class="n">log</span><span class="o">.</span><span
class="na">info</span><span class="o">(</span><span class="s">"Response:
{}"</span><span class="o">,</span> <span class="n">response</span><span
class="o">.</span><span class="na">asString</span><span
class="o">());</span></code></pre>
+<pre class="rouge highlight"><code data-lang="java"><span
class="nc">CallFailedRuntimeException</span> <span class="n">exception</span>
<span class="o">=</span> <span class="n">fail</span><span class="o">(()</span>
<span class="o">-></span> <span class="n">loanHelper</span><span
class="o">.</span><span class="na">applyForLoan</span><span
class="o">(</span><span class="n">request</span><span class="o">));</span>
+<span class="n">log</span><span class="o">.</span><span
class="na">info</span><span class="o">(</span><span class="s">"status={}
body={}"</span><span class="o">,</span> <span class="n">exception</span><span
class="o">.</span><span class="na">getStatus</span><span class="o">(),</span>
<span class="n">exception</span><span class="o">.</span><span
class="na">getResponseBody</span><span class="o">());</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>To see the requests and responses themselves, build a client with debug
logging enabled:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span
class="nc">FineractFeignClient</span> <span class="n">debugClient</span> <span
class="o">=</span> <span class="nc">FineractFeignClientHelper</span>
+ <span class="o">.</span><span
class="na">createNewFineractFeignClient</span><span class="o">(</span><span
class="n">username</span><span class="o">,</span> <span
class="n">password</span><span class="o">,</span> <span
class="kc">true</span><span class="o">);</span></code></pre>
</div>
</div>
</div>
@@ -76463,7 +75592,7 @@ mysql <span class="nt">-u</span> root <span
class="nt">-pmysql</span> fineract_d
<div class="ulist">
<ul>
<li>
-<p>Extend appropriate base classes (<code>BaseLoanIntegrationTest</code>,
<code>IntegrationTest</code>)</p>
+<p>Extend <code>FeignLoanTestBase</code> for loan tests, or
<code>FeignSavingsTestBase</code> for savings tests</p>
</li>
<li>
<p>Use descriptive test method names that explain what is being tested</p>
@@ -76472,7 +75601,7 @@ mysql <span class="nt">-u</span> root <span
class="nt">-pmysql</span> fineract_d
<p>Group related tests in the same test class</p>
</li>
<li>
-<p>Use <code>@BeforeEach</code> for setup and <code>@AfterEach</code> for
cleanup</p>
+<p>Rely on the base class for setup; a Feign test needs no
<code>@BeforeEach</code> block to build a client</p>
</li>
<li>
<p>Follow existing naming conventions</p>
@@ -76607,6 +75736,500 @@ mysql <span class="nt">-u</span> root <span
class="nt">-pmysql</span> fineract_d
</div>
</div>
</div>
+<div class="sect2">
+<h3 id="_feign_integration_tests"><a class="link"
href="#_feign_integration_tests">Feign Integration Tests</a></h3>
+<div class="sect3">
+<h4 id="_overview_39"><a class="link" href="#_overview_39">Overview</a></h4>
+<div class="paragraph">
+<p>Apache Fineract is migrating its integration tests from REST Assured to the
generated Feign client (<code>fineract-client-feign</code>), so that tests
drive the server through the same strongly typed, OpenAPI-derived models that
API consumers use. A test written against the generated client stops compiling
when the API contract changes, rather than failing at runtime — or, worse,
continuing to pass against a payload the server no longer produces.</p>
+</div>
+<div class="paragraph">
+<p>The shared machinery is the <code>client/feign</code> package tree — the
<code>FeignLoanTestBase</code> and <code>FeignSavingsTestBase</code> base
classes and the helpers under <code>client/feign/helpers</code> — together with
the <code>FeignIntegrationTest</code> root base class one directory above it
and the <code>FineractFeignClientHelper</code> client factory in
<code>common</code>. Most migrated tests sit alongside the unmigrated ones in
the <code>org.apache.fineract.integrationt [...]
+</div>
+<div class="paragraph">
+<p>A Checkstyle rule keeps REST Assured out of that machinery rather than out
of individual tests, because a leak there is inherited by every test extending
a Feign base class, while a leak in one test file affects only that test.</p>
+</div>
+<div class="paragraph">
+<p>This chapter covers the guardrail and the conventions that apply to tests
written against the Feign client. For prerequisites, database setup, and how to
run a suite, see the <a href="#_integration_testing">Integration Testing</a>
chapter.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_the_guardrail"><a class="link" href="#_the_guardrail">The
Guardrail</a></h4>
+<div class="sect4">
+<h5 id="_how_it_works_4"><a class="link" href="#_how_it_works_4">How It
Works</a></h5>
+<div class="paragraph">
+<p>An <code>IllegalImport</code> check in
<code>config/checkstyle/checkstyle.xml</code> rejects any
<code>io.restassured</code> import, and a <code>SuppressionSingleFilter</code>
narrows it to the shared machinery:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="xml"><span
class="nt"><module</span> <span class="na">name=</span><span
class="s">"SuppressionSingleFilter"</span><span class="nt">></span>
+ <span class="nt"><property</span> <span class="na">name=</span><span
class="s">"id"</span> <span class="na">value=</span><span
class="s">"FeignNoRestAssured"</span><span class="nt">/></span>
+ <span class="nt"><property</span> <span class="na">name=</span><span
class="s">"files"</span>
+ <span class="na">value=</span><span
class="s">"^(?!.*[\\/]integrationtests[\\/](?:client[\\/]feign[\\/]|client[\\/]FeignIntegrationTest\.java|common[\\/]FineractFeignClientHelper\.java)).*$"</span><span
class="nt">/></span>
+<span class="nt"></module></span></code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="xml"><span
class="nt"><module</span> <span class="na">name=</span><span
class="s">"IllegalImport"</span><span class="nt">></span>
+ <span class="nt"><property</span> <span class="na">name=</span><span
class="s">"id"</span> <span class="na">value=</span><span
class="s">"FeignNoRestAssured"</span> <span class="nt">/></span>
+ <span class="nt"><property</span> <span class="na">name=</span><span
class="s">"regexp"</span> <span class="na">value=</span><span
class="s">"true"</span> <span class="nt">/></span>
+ <span class="nt"><property</span> <span class="na">name=</span><span
class="s">"illegalPkgs"</span> <span class="na">value=</span><span
class="s">"^io\.restassured"</span> <span class="nt">/></span>
+ <span class="nt"><message</span> <span class="na">key=</span><span
class="s">"import.illegal"</span>
+ <span class="na">value=</span><span class="s">"Feign integration
tests must call the server through the generated Feign client, not REST
Assured: ''{0}''"</span> <span class="nt">/></span>
+<span class="nt"></module></span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The filter sits at <code>Checker</code> level and the check inside
<code>TreeWalker</code>. Both carry the same <code>id</code>, so the filter
applies to this check alone and leaves every other check untouched. The
<code>files</code> pattern suppresses the check on every path that is not one
of the three guarded locations, which is what limits its reach.</p>
+</div>
+<div class="paragraph">
+<p>The check runs as part of <code>checkstyleTest</code>, which the
<code>build-quality-checks.yml</code> workflow executes on every pull request.
A violation fails the build.</p>
+</div>
+<div class="paragraph">
+<p>Migrated tests outside those locations are not covered. They live in the
root <code>org.apache.fineract.integrationtests</code> package next to the
tests that still use REST Assured, and Checkstyle scopes by file path, not by
supertype, so a rule covering them would fail on their unmigrated neighbours.
The scope widens as the migration progresses.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_what_the_check_does_and_does_not_catch"><a class="link"
href="#_what_the_check_does_and_does_not_catch">What the Check Does and Does
Not Catch</a></h5>
+<div class="paragraph">
+<p>The <code>Utils.performServerGet</code>, <code>performServerPost</code>,
<code>performServerPut</code> and <code>performServerDelete</code> helpers in
<code>org.apache.fineract.integrationtests.common</code> are the other common
route back to REST Assured. Every one of those overloads takes an
<code>io.restassured.specification.RequestSpecification</code> as its first
parameter, so reaching them normally requires the forbidden import.</p>
+</div>
+<div class="paragraph">
+<p>The check reads import statements only, so it is a deterrent rather than a
proof. Two routes get past it: a fully qualified reference such as
<code>io.restassured.RestAssured.baseURI</code>, and a <code>var</code> that
infers a REST Assured type from another method’s return value without
ever naming it. Neither appears in the tree today, and neither is something a
contributor writes by accident, but a reviewer should not treat a green build
as evidence that no REST Assured call [...]
+</div>
+<div class="paragraph">
+<p>Utility methods on <code>Utils</code> that do not touch the transport —
<code>uniqueRandomStringGenerator</code>, <code>getLocalDateOfTenant</code>,
<code>getDoubleValue</code>, <code>dateFormatter</code> — are unaffected and
are used widely by migrated tests.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_running_locally_2"><a class="link" href="#_running_locally_2">Running
Locally</a></h5>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="bash">./gradlew
:integration-tests:checkstyleTest</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The report is written to
<code>integration-tests/build/reports/checkstyle/</code>.</p>
+</div>
+<div class="paragraph">
+<p>The task compiles the test sources first, which for this module means
generating both clients. Cargo is not involved:
<code>integration-tests/build.gradle</code> attaches it to the
<code>test</code> task alone, so a static check never starts a container.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_when_the_check_fires"><a class="link"
href="#_when_the_check_fires">When the Check Fires</a></h5>
+<div class="paragraph">
+<p>A violation means a call is being made outside the generated client.
Resolve it in this order:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p><strong>The endpoint is in the generated client.</strong> Use it. This is
the common case — the generated client covers the public API surface, including
the <code>/v1/internal</code> endpoints used by tests.</p>
+</li>
+<li>
+<p><strong>The endpoint is in the client, but the generated model is missing a
field you need.</strong> Fix the schema at source. See <a
href="#feign-fix-model-at-source">Fixing a Generated Model at Source</a>.</p>
+</li>
+<li>
+<p><strong>The generated client cannot express the call.</strong> Where an
endpoint declares no response schema the generator types the result as a bare
<code>String</code> — <code>retrieveOneSavingsAccountTransaction</code> is one
example — and some multi-step commands are not modelled at all. Give the
endpoint an <code>@ApiResponse</code> schema where you can (IT-06); otherwise
call it over plain HTTP and add a Javadoc comment stating why the typed client
cannot express it.</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>Suppressing the rule, dropping the assertion, or substituting a different
field are not resolutions. See rules IT-05 and IT-09.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_writing_a_feign_integration_test"><a class="link"
href="#_writing_a_feign_integration_test">Writing a Feign Integration
Test</a></h4>
+<div class="sect4">
+<h5 id="_test_class_layout"><a class="link" href="#_test_class_layout">Test
Class Layout</a></h5>
+<div class="paragraph">
+<p>Loan tests extend <code>FeignLoanTestBase</code>, which supplies the
client, the shared helpers, and the accounting and schedule verification
methods. Savings tests extend <code>FeignSavingsTestBase</code>.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span
class="kn">package</span> <span
class="nn">org.apache.fineract.integrationtests.client.feign.tests</span><span
class="o">;</span>
+
+<span class="kn">import</span> <span class="nn">static</span> <span
class="n">org</span><span class="o">.</span><span class="na">junit</span><span
class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span
class="na">api</span><span class="o">.</span><span
class="na">Assertions</span><span class="o">.</span><span
class="na">assertEquals</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">static</span> <span
class="n">org</span><span class="o">.</span><span class="na">junit</span><span
class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span
class="na">api</span><span class="o">.</span><span
class="na">Assertions</span><span class="o">.</span><span
class="na">assertNotNull</span><span class="o">;</span>
+
+<span class="kn">import</span> <span
class="nn">org.apache.fineract.client.models.GetLoansLoanIdResponse</span><span
class="o">;</span>
+<span class="kn">import</span> <span
class="nn">org.apache.fineract.client.models.GetLoansLoanIdStatus</span><span
class="o">;</span>
+<span class="kn">import</span> <span
class="nn">org.apache.fineract.client.models.PostLoanProductsRequest</span><span
class="o">;</span>
+<span class="kn">import</span> <span
class="nn">org.apache.fineract.integrationtests.client.feign.FeignLoanTestBase</span><span
class="o">;</span>
+<span class="kn">import</span> <span
class="nn">org.apache.fineract.integrationtests.common.Utils</span><span
class="o">;</span>
+<span class="kn">import</span> <span
class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>
+
+<span class="kd">public</span> <span class="kd">class</span> <span
class="nc">FeignLoanCreationTest</span> <span class="kd">extends</span> <span
class="nc">FeignLoanTestBase</span> <span class="o">{</span>
+
+ <span class="nd">@Test</span>
+ <span class="kt">void</span> <span
class="nf">testLoanRepayment_FullRepayment</span><span class="o">()</span>
<span class="o">{</span>
+ <span class="nc">Long</span> <span class="n">clientId</span> <span
class="o">=</span> <span class="n">createClient</span><span class="o">();</span>
+ <span class="nc">PostLoanProductsRequest</span> <span
class="n">productRequest</span> <span class="o">=</span> <span
class="n">onePeriod30DaysNoInterest</span><span class="o">();</span>
+ <span class="nc">Long</span> <span class="n">productId</span> <span
class="o">=</span> <span class="n">createLoanProduct</span><span
class="o">(</span><span class="n">productRequest</span><span class="o">);</span>
+ <span class="nc">String</span> <span class="n">todayDate</span> <span
class="o">=</span> <span class="nc">Utils</span><span class="o">.</span><span
class="na">dateFormatter</span><span class="o">.</span><span
class="na">format</span><span class="o">(</span><span
class="nc">Utils</span><span class="o">.</span><span
class="na">getLocalDateOfTenant</span><span class="o">());</span>
+ <span class="nc">Long</span> <span class="n">loanId</span> <span
class="o">=</span> <span class="n">createApproveAndDisburseLoan</span><span
class="o">(</span><span class="n">clientId</span><span class="o">,</span> <span
class="n">productId</span><span class="o">,</span> <span
class="n">todayDate</span><span class="o">,</span> <span
class="mf">1000.0</span><span class="o">,</span> <span class="mi">1</span><span
class="o">);</span>
+
+ <span class="nc">Long</span> <span class="n">repaymentId</span> <span
class="o">=</span> <span class="n">addRepayment</span><span
class="o">(</span><span class="n">loanId</span><span class="o">,</span> <span
class="n">repayment</span><span class="o">(</span><span
class="mf">1000.0</span><span class="o">,</span> <span
class="n">todayDate</span><span class="o">));</span>
+ <span class="n">assertNotNull</span><span class="o">(</span><span
class="n">repaymentId</span><span class="o">);</span>
+
+ <span class="nc">GetLoansLoanIdResponse</span> <span
class="n">loan</span> <span class="o">=</span> <span
class="n">getLoanDetails</span><span class="o">(</span><span
class="n">loanId</span><span class="o">);</span>
+ <span class="n">verifyLoanStatus</span><span class="o">(</span><span
class="n">loan</span><span class="o">,</span> <span
class="nl">GetLoansLoanIdStatus:</span><span class="o">:</span><span
class="n">getClosedObligationsMet</span><span class="o">);</span>
+ <span class="n">assertEquals</span><span class="o">(</span><span
class="mf">0.0</span><span class="o">,</span> <span
class="nc">Utils</span><span class="o">.</span><span
class="na">getDoubleValue</span><span class="o">(</span><span
class="n">loan</span><span class="o">.</span><span
class="na">getSummary</span><span class="o">().</span><span
class="na">getTotalOutstanding</span><span class="o">()));</span>
+ <span class="o">}</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p><code>FeignLoanTestBase</code> assigns its helper fields in a
<code>@BeforeAll</code> named <code>setupHelpers</code>. A subclass that
declares its own <code>@BeforeAll</code> must give it a different name — a
static method with the same signature hides the inherited one, and the helpers
are then never assigned.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_obtaining_the_client_directly"><a class="link"
href="#_obtaining_the_client_directly">Obtaining the Client Directly</a></h5>
+<div class="paragraph">
+<p>Inside a test, <code>fineractClient()</code> returns the client the base
class is using. <code>FineractFeignClientHelper.getFineractFeignClient()</code>
returns the same shared instance, authenticated with the default backend
credentials, and is what helpers outside the base class use. Call
<code>createNewFineractFeignClient(username, password)</code> when a test needs
a different user.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_calling_the_api"><a class="link" href="#_calling_the_api">Calling the
API</a></h5>
+<div class="paragraph">
+<p>Wrap calls in the <code>FeignCalls</code> helpers from
<code>org.apache.fineract.client.feign.util</code> rather than invoking the API
interface directly:</p>
+</div>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 75%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Method</th>
+<th class="tableblock halign-left valign-top">Use</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>ok(…​)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A call
expected to succeed, returning a typed body.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>executeVoid(…​)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A call
expected to succeed with no response body.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>fail(…​)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A call
expected to fail; returns the <code>CallFailedRuntimeException</code> for
assertion.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>failVoid(…​)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">As
<code>fail(…​)</code>, for a call with no response body.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>execute(…​)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A call
whose raw <code>FeignException</code> the test handles itself.</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>A direct invocation throws a raw <code>FeignException</code>, which carries
the HTTP status but not the wrapped error detail the assertion helpers expect.
<code>ok(…​)</code> and <code>fail(…​)</code> translate
it into <code>CallFailedRuntimeException</code>.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_asserting_failures"><a class="link"
href="#_asserting_failures">Asserting Failures</a></h5>
+<div class="paragraph">
+<p><code>FeignLoanTestBase.assertErrorGlobalisationCode(exception,
expectedCode)</code> asserts the specific error code. The top-level
<code>userMessageGlobalisationCode</code> is often a generic wrapper —
<code>validation.msg.validation.errors.exist</code> for a validation failure,
<code>validation.msg.domain.rule.violation</code> for a domain rule — with the
meaningful code in the first element of the <code>errors</code> array. The
helper returns the top-level code unless it is one of [...]
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span
class="nc">CallFailedRuntimeException</span> <span class="n">exception</span>
<span class="o">=</span> <span class="n">fail</span><span class="o">(</span>
+ <span class="o">()</span> <span class="o">-></span> <span
class="n">fineractClient</span><span class="o">().</span><span
class="na">loans</span><span class="o">().</span><span
class="na">handleCommandsLoan</span><span class="o">(</span><span
class="n">loanId</span><span class="o">,</span> <span
class="n">request</span><span class="o">,</span> <span
class="nc">Map</span><span class="o">.</span><span class="na">of</span><span
class="o">(</span><span class="s">"command"</span><sp [...]
+<span class="n">assertEquals</span><span class="o">(</span><span
class="mi">403</span><span class="o">,</span> <span
class="n">exception</span><span class="o">.</span><span
class="na">getStatus</span><span class="o">());</span>
+<span class="n">assertErrorGlobalisationCode</span><span
class="o">(</span><span class="n">exception</span><span class="o">,</span>
<span class="s">"error.msg.loan.approval.cannot.be.a.future.date"</span><span
class="o">);</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_conventions"><a class="link" href="#_conventions">Conventions</a></h4>
+<div class="paragraph">
+<p>The following rules are drawn from review feedback on the migration pull
requests. They are referenced by identifier in code review.</p>
+</div>
+<div class="sect4">
+<h5 id="_transport"><a class="link" href="#_transport">Transport</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 75%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Rule</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-01</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Call the
server through the generated Feign client. REST Assured is not permitted in the
<code>…​client.feign</code> package tree; the Checkstyle rule above
enforces this.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-02</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Use the
<code>FeignCalls</code> helpers rather than invoking the generated API
interface directly, so failures arrive as
<code>CallFailedRuntimeException</code>.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-03</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Use the
Feign client, not the legacy Retrofit client’s <code>Calls</code> or
<code>.legacy</code> accessors.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-04</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Plain HTTP
is acceptable only where the generated client cannot express the call, and only
with a Javadoc comment recording why.</p></td>
+</tr>
+</tbody>
+</table>
+<div class="admonitionblock caution">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-caution" title="Caution"></i>
+</td>
+<td class="content">
+The Retrofit and Feign SDKs both publish models under
<code>org.apache.fineract.client.models</code>. In
<code>integration-tests</code> both are on the test classpath, and the one
declared first in <code>dependencies.gradle</code> wins. When a model change
appears to have no effect at runtime, check which SDK supplied the class.
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="feign-schema-and-sdk"><a class="link"
href="#feign-schema-and-sdk">Schema and SDK</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 75%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Rule</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-05</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">When a
generated model lacks a field the test needs, add it to the corresponding
<code>*ApiResourceSwagger</code> DTO and regenerate. Do not work around the gap
in the test.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-06</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Where an
endpoint declares no response schema, give it one via <code>@ApiResponse</code>
rather than introducing a hand-written client interface.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-07</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Schema
changes must respect the API compatibility rules. See <a
href="#_api_backward_compatibility">API Backward Compatibility</a>, including
its guidance on intentional breaking changes.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-08</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">To send an
explicit JSON <code>null</code>, mark the property <code>nullable</code> in the
schema; the generator emits <code>JsonNullable<T></code>. Do not
hand-roll a request model.</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>After changing a Swagger DTO, regenerate the client and compile every
module that consumes it, not only the one under test:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="bash"><span class="c">#
Regenerate both clients</span>
+./gradlew :fineract-client:cleanupGeneratedJavaFiles <span class="se">\</span>
+ :fineract-client-feign:cleanupGeneratedJavaFiles
+
+<span class="c"># Then compile the consumers</span>
+./gradlew :fineract-provider:compileTestJava <span class="se">\</span>
+ :integration-tests:compileTestJava <span class="se">\</span>
+ <span class="nt">-PcargoDisabled</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p><code>cleanupGeneratedJavaFiles</code> depends on
<code>buildJavaSdk</code>, so it regenerates and then copies the result into
<code>build/generated/java</code>. Running <code>buildJavaSdk</code> on its own
stops before that copy and leaves the sources the compiler actually uses
unchanged. Neither task compiles anything downstream, which is why the second
command is needed — a schema change that breaks a consumer is otherwise found
only in CI.</p>
+</div>
+<div class="paragraph">
+<p>Regenerate both clients together. <code>integration-tests</code> compiles
against both, and changing one alone leaves the two disagreeing about the same
model class.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_assertions"><a class="link" href="#_assertions">Assertions</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 75%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Rule</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-09</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Never
weaken or delete a verification because the generated model does not expose the
field. Fix the model (IT-05) or keep the check by another route.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-10</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Assert the
specific status code and error code, not merely that the call threw.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-11</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Verify an
unordered collection by removing each expected element from a mutable copy of
the actual collection, then asserting the copy is empty. A size check combined
with <code>anyMatch</code> passes on duplicates.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-12</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A
<code>null</code> expected value means "assert this is null". Do not redefine
it to mean "skip this check".</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-13</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A
migration must not change an expected value. If a migrated test needs a
different number to pass, establish which value the server actually produces
before changing it.</p></td>
+</tr>
+</tbody>
+</table>
+<div class="listingblock">
+<div class="title">Verifying an unordered collection</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span
class="nc">List</span><span class="o"><</span><span
class="nc">JournalEntry</span><span class="o">></span> <span
class="n">remaining</span> <span class="o">=</span> <span class="k">new</span>
<span class="nc">ArrayList</span><span class="o"><>(</span><span
class="n">actualEntries</span><span class="o">);</span>
+<span class="k">for</span> <span class="o">(</span><span
class="nc">JournalEntry</span> <span class="n">expected</span> <span
class="o">:</span> <span class="n">expectedEntries</span><span
class="o">)</span> <span class="o">{</span>
+ <span class="nc">Optional</span><span class="o"><</span><span
class="nc">JournalEntry</span><span class="o">></span> <span
class="n">match</span> <span class="o">=</span> <span
class="n">remaining</span><span class="o">.</span><span
class="na">stream</span><span class="o">().</span><span
class="na">filter</span><span class="o">(</span><span class="n">a</span> <span
class="o">-></span> <span class="n">matches</span><span
class="o">(</span><span class="n">a</span><span class=" [...]
+ <span class="n">assertTrue</span><span class="o">(</span><span
class="n">match</span><span class="o">.</span><span
class="na">isPresent</span><span class="o">(),</span> <span class="s">"Missing
entry: "</span> <span class="o">+</span> <span class="n">expected</span><span
class="o">);</span>
+ <span class="n">remaining</span><span class="o">.</span><span
class="na">remove</span><span class="o">(</span><span
class="n">match</span><span class="o">.</span><span class="na">get</span><span
class="o">());</span>
+<span class="o">}</span>
+<span class="n">assertTrue</span><span class="o">(</span><span
class="n">remaining</span><span class="o">.</span><span
class="na">isEmpty</span><span class="o">(),</span> <span class="s">"Unexpected
entries: "</span> <span class="o">+</span> <span
class="n">remaining</span><span class="o">);</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_test_structure_2"><a class="link" href="#_test_structure_2">Test
Structure</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 75%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Rule</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-14</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A helper
used by one test class belongs in that class. Only behaviour shared across
classes belongs on the base class or in a helper under
<code>client/feign/helpers</code>.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-15</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Return the
response model from a helper, not just the identifier, so callers can assert on
the rest of the response without a second request.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-16</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Use the
generated enums and the shared constants in
<code>client/feign/modules/FeignTestConstants</code>. Do not repeat literal
codes and identifiers.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-17</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Name
variables and helper methods for what they hold or do in domain terms, not for
their type.</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect3">
+<h4 id="feign-fix-model-at-source"><a class="link"
href="#feign-fix-model-at-source">Fixing a Generated Model at Source</a></h4>
+<div class="paragraph">
+<p>When a field the server accepts or returns is absent from the generated
model, the fix belongs in the Swagger DTO on the server, not in the test.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Locate the <code>*ApiResourceSwagger</code> class for the resource — for
example <code>LoanProductsApiResourceSwagger</code> for
<code>/loanproducts</code>.</p>
+</li>
+<li>
+<p>Add the field to the request or response DTO, matching the name and type
the server already reads or writes.</p>
+</li>
+<li>
+<p>Regenerate both SDKs and compile every consuming module, using the command
under <a href="#feign-schema-and-sdk">Schema and SDK</a>.</p>
+</li>
+<li>
+<p>Confirm the API compatibility check still passes, or document the breaking
change as described in <a href="#_api_backward_compatibility">API Backward
Compatibility</a>.</p>
+</li>
+<li>
+<p>Replace the workaround in the test with the generated accessor.</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>Adding a field to a response schema and widening a request type are both
additive from the perspective of an existing consumer, but the compatibility
checker reports some widening changes as violations. <a
href="#_api_backward_compatibility">API Backward Compatibility</a> describes
how to handle a violation that is deliberate.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_verifying_a_change"><a class="link"
href="#_verifying_a_change">Verifying a Change</a></h4>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 75%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Rule</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-18</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">Compilation and <code>spotlessCheck</code> are not
verification. Run the tests you touched against a running instance.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-19</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Pass
<code>-PcargoDisabled</code> when running against an instance you started
yourself, otherwise the Cargo plugin starts a second Tomcat.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">IT-20</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Before
attributing a failure to your change, run the test at the base commit on the
same instance. Some tests fail independently of the change under
review.</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>Against a locally running instance:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="bash">./gradlew
:integration-tests:test <span class="nt">-PcargoDisabled</span> <span
class="se">\</span>
+ <span class="nt">--tests</span> FeignLoanCreationTest</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Without <code>-PcargoDisabled</code> the Cargo plugin downloads Tomcat,
deploys <code>fineract-provider.war</code>, and runs the suite against that
container instead.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_tool_reference_2"><a class="link" href="#_tool_reference_2">Tool
Reference</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Checkstyle</strong>: <a
href="https://checkstyle.org/">checkstyle</a> 13.4.2, configured in
<code>config/checkstyle/checkstyle.xml</code></p>
+</li>
+<li>
+<p><strong>IllegalImport</strong>: <a
href="https://checkstyle.org/checks/imports/illegalimport.html">check
reference</a></p>
+</li>
+<li>
+<p><strong>SuppressionSingleFilter</strong>: <a
href="https://checkstyle.org/filters/suppressionsinglefilter.html">filter
reference</a></p>
+</li>
+<li>
+<p><strong>Workflow</strong>:
<code>.github/workflows/build-quality-checks.yml</code></p>
+</li>
+<li>
+<p><strong>Generated client</strong>: <code>fineract-client-feign</code></p>
+</li>
+<li>
+<p><strong>Migrated tests</strong>:
<code>integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign</code></p>
+</li>
+</ul>
+</div>
+</div>
+</div>
</div>
</div>
<div class="sect1">
@@ -77592,7 +77215,7 @@ More info at <a
href="https://docs.asciidoctor.org/asciidoc/latest/stem/stem" cl
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PScxNDdweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjQxMHB4O2hlaWdodDoxNDdweDtiYWNrZ3JvdW5kOiNGRkZGRkY7JyB2ZXJzaW9uPScxLjEnIHZpZXdCb3g9JzAgMCA0MTAgMTQ3JyB3aWR0aD0nNDEwcHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48Zz48
[...]
</div>
-<div class="title">Figure 14. Release Schedule</div>
+<div class="title">Figure 17. Release Schedule</div>
</div>
<div class="admonitionblock tip">
<table>
@@ -78276,7 +77899,7 @@ Fineract release plugin Gradle tasks are experimental
and incomplete.
<div class="content">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PScxNjI2cHgnIHByZXNlcnZlQXNwZWN0UmF0aW89J25vbmUnIHN0eWxlPSd3aWR0aDozNjlweDtoZWlnaHQ6MTYyNnB4O2JhY2tncm91bmQ6I0ZGRkZGRjsnIHZlcnNpb249JzEuMScgdmlld0JveD0nMCAwIDM2OSAxNjI2JyB3aWR0aD0nMzY5cHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48
[...]
</div>
-<div class="title">Figure 15. Release Process Diagram</div>
+<div class="title">Figure 18. Release Process Diagram</div>
</div>
<div class="sect3">
<h4 id="_step_1_heads_up_email"><a class="link"
href="#_step_1_heads_up_email">Step 1: Heads-Up Email</a></h4>
@@ -80466,7 +80089,7 @@ Find more information at <a
href="https://infra.apache.org/publishing-maven-arti
<div id="footer">
<div id="footer-text">
Version 1.16.0-SNAPSHOT<br>
-Last updated Sat Aug 29 20:04:15 PDT 2026 07:15:38 -0700
+Last updated Mon Aug 31 09:19:07 PDT 2026 07:15:38 -0700
</div>
</div>
</body>