summaryrefslogtreecommitdiffstats
path: root/tests/cepis-tests.cob
diff options
context:
space:
mode:
Diffstat (limited to 'tests/cepis-tests.cob')
-rw-r--r--tests/cepis-tests.cob761
1 files changed, 761 insertions, 0 deletions
diff --git a/tests/cepis-tests.cob b/tests/cepis-tests.cob
new file mode 100644
index 0000000..4bc1a11
--- /dev/null
+++ b/tests/cepis-tests.cob
@@ -0,0 +1,761 @@
+IDENTIFICATION DIVISION.
+PROGRAM-ID. CEPIS-TESTS.
+
+*> ---------------------------------------------------------------------
+*> The suite. Run it through tests/server.py, which binds a loopback
+*> port, exports CEPIS_TEST_URL and runs this program:
+*>
+*> python3 tests/server.py ./build/cepis-tests
+*>
+*> Every test goes through the real transport against that server, so
+*> what is exercised is the code that runs in production -- request
+*> construction, header handling and error-body reading included. It
+*> doubles as the test that CFG-BASE-URL works: without it none of this
+*> would reach anything.
+*>
+*> The process exits non-zero if any check fails.
+*> ---------------------------------------------------------------------
+
+DATA DIVISION.
+WORKING-STORAGE SECTION.
+COPY "cepis.cpy".
+COPY "cepis-http.cpy".
+
+01 WS-BASE PIC X(200).
+01 WS-CHECKS PIC 9(4) VALUE 0.
+01 WS-FAILURES PIC 9(4) VALUE 0.
+01 WS-LABEL PIC X(56).
+01 WS-DETAIL PIC X(200).
+01 WS-OK PIC X.
+01 WS-TALLY PIC 9(4).
+01 WS-I PIC 9(4).
+01 WS-COUNT PIC 9(9).
+01 WS-COUNT-BEFORE PIC 9(9).
+01 WS-COUNT-PATH PIC X(40).
+01 WS-NUM8 PIC 9(8).
+01 WS-T0 PIC S9(18) COMP-5.
+01 WS-T1 PIC S9(18) COMP-5.
+01 WS-ELAPSED PIC S9(18).
+01 WS-SAVED-ATTEMPTS PIC 9(4).
+01 WS-SAVED-RETRY PIC S9(9).
+
+01 WS-GOOD-TABLE.
+ 05 FILLER PIC X(20) VALUE "01001000".
+ 05 FILLER PIC X(20) VALUE "01001-000".
+ 05 FILLER PIC X(20) VALUE " 01001-000 ".
+ 05 FILLER PIC X(20) VALUE "01001.000".
+01 WS-GOODS REDEFINES WS-GOOD-TABLE.
+ 05 WS-GOOD OCCURS 4 TIMES PIC X(20).
+
+01 WS-BAD-TABLE.
+ 05 FILLER PIC X(20) VALUE SPACES.
+ 05 FILLER PIC X(20) VALUE "123".
+ 05 FILLER PIC X(20) VALUE "0100100a".
+ 05 FILLER PIC X(20) VALUE "010010000".
+ 05 FILLER PIC X(20) VALUE "abcdefgh".
+01 WS-BADS REDEFINES WS-BAD-TABLE.
+ 05 WS-BAD OCCURS 5 TIMES PIC X(20).
+
+PROCEDURE DIVISION.
+
+MAIN-START.
+ MOVE SPACES TO WS-BASE
+ ACCEPT WS-BASE FROM ENVIRONMENT "CEPIS_TEST_URL"
+ IF WS-BASE = SPACES
+ DISPLAY "CEPIS_TEST_URL is not set -- run through "
+ "tests/server.py"
+ MOVE 2 TO RETURN-CODE
+ STOP RUN
+ END-IF
+ DISPLAY "cepis 0.1.0 test suite against " FUNCTION TRIM (WS-BASE)
+ DISPLAY " "
+
+ PERFORM T-NORMALIZE-ACCEPTS
+ PERFORM T-NORMALIZE-REJECTS
+ PERFORM T-LOOKUP-SUCCESS
+ PERFORM T-GEO-PRECISE
+ PERFORM T-GEO-CITY-WIDE
+ PERFORM T-GEO-MALFORMED
+ PERFORM T-UNKNOWN-FIELDS
+ PERFORM T-LONG-VALUE-FLAGGED
+ PERFORM T-NOT-FOUND
+ PERFORM T-NOT-FOUND-NOT-RETRIED
+ PERFORM T-INVALID-NEVER-LEAVES
+ PERFORM T-API-400
+ PERFORM T-NOT-JSON
+ PERFORM T-RETRY-THEN-SUCCESS
+ PERFORM T-RETRY-EXHAUSTED
+ PERFORM T-ONE-ATTEMPT-NO-RETRY
+ PERFORM T-RATE-LIMITED
+ PERFORM T-RETRY-AFTER-CAPPED
+ PERFORM T-RETRY-AFTER-HTTP-DATE
+ PERFORM T-NETWORK-ERROR
+ PERFORM T-TOTAL-TIMEOUT
+ PERFORM T-BATCH-ORDER
+ PERFORM T-BATCH-QUOTING
+ PERFORM T-BATCH-MALFORMED-ENTRY
+ PERFORM T-BATCH-EMPTY
+ PERFORM T-BATCH-TRUNCATED
+ PERFORM T-BATCH-CHUNKING
+ PERFORM T-BATCH-OVER-CAPACITY
+ PERFORM T-HEALTH
+ PERFORM T-HEALTH-DEGRADED
+ PERFORM T-TRAILING-SLASH
+ PERFORM T-DEFAULT-USER-AGENT
+
+ DISPLAY " "
+ DISPLAY WS-CHECKS " checks, " WS-FAILURES " failed"
+ IF WS-FAILURES > 0
+ MOVE 1 TO RETURN-CODE
+ ELSE
+ MOVE 0 TO RETURN-CODE
+ END-IF
+ STOP RUN.
+
+*> ===================================================================
+*> Fixtures
+*> ===================================================================
+FRESH-CONFIG.
+ CALL "CEPIS-DEFAULT-CONFIG" USING CEPIS-CONFIG
+ MOVE WS-BASE TO CFG-BASE-URL
+ MOVE 5000 TO CFG-TIMEOUT-MS
+ MOVE 3 TO CFG-MAX-ATTEMPTS
+ MOVE 1 TO CFG-BACKOFF-BASE-MS
+ MOVE 10 TO CFG-BACKOFF-MAX-MS
+ MOVE 0 TO CFG-TOTAL-TIMEOUT-MS.
+
+CHECK.
+ ADD 1 TO WS-CHECKS
+ IF WS-OK = "Y"
+ DISPLAY "ok " WS-LABEL
+ ELSE
+ ADD 1 TO WS-FAILURES
+ DISPLAY "FAIL " WS-LABEL " -- " FUNCTION TRIM (WS-DETAIL)
+ END-IF
+ MOVE SPACES TO WS-DETAIL
+ MOVE "N" TO WS-OK.
+
+*> The fake server counts requests per path; that is how a 404 not being
+*> retried, or 501 CEPs becoming two requests, is proved rather than
+*> assumed.
+COUNT-FOR.
+ PERFORM FRESH-CONFIG
+ MOVE 1 TO CFG-MAX-ATTEMPTS
+ MOVE SPACES TO HTTPQ-PATH
+ STRING "/count?path=" FUNCTION TRIM (WS-COUNT-PATH)
+ DELIMITED BY SIZE INTO HTTPQ-PATH
+ END-STRING
+ COMPUTE HTTPQ-PATH-LEN =
+ 12 + FUNCTION LENGTH (FUNCTION TRIM (WS-COUNT-PATH))
+ MOVE "N" TO HTTPQ-ALLOW-503
+ MOVE 0 TO WS-COUNT
+ CALL "CEPIS-REQUEST" USING CEPIS-CONFIG CEPIS-HTTP-REQUEST
+ CEPIS-HTTP-RESPONSE CEPIS-STATUS
+ END-CALL
+ IF CEPIS-OK AND HTTP-BODY-LEN > 0
+ COMPUTE WS-COUNT =
+ FUNCTION NUMVAL (HTTP-BODY (1:HTTP-BODY-LEN))
+ END-IF.
+
+*> ===================================================================
+*> Local validation
+*> ===================================================================
+T-NORMALIZE-ACCEPTS.
+ PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 4
+ MOVE WS-GOOD (WS-I) TO CEPIS-CEP-INPUT
+ CALL "CEPIS-NORMALIZE-CEP"
+ USING CEPIS-CEP-INPUT CEPIS-CEP CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND CEPIS-CEP = "01001000"
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE SPACES TO WS-LABEL
+ STRING "normalize accepts " WS-GOOD (WS-I)
+ DELIMITED BY SIZE INTO WS-LABEL
+ END-STRING
+ MOVE CEPIS-CEP TO WS-DETAIL
+ PERFORM CHECK
+ END-PERFORM.
+
+T-NORMALIZE-REJECTS.
+ PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 5
+ MOVE WS-BAD (WS-I) TO CEPIS-CEP-INPUT
+ CALL "CEPIS-NORMALIZE-CEP"
+ USING CEPIS-CEP-INPUT CEPIS-CEP CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-INVALID-CEP AND CEPIS-CEP = SPACES
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE SPACES TO WS-LABEL
+ STRING "normalize rejects '" WS-BAD (WS-I) "'"
+ DELIMITED BY SIZE INTO WS-LABEL
+ END-STRING
+ MOVE CEPIS-STATUS-MESSAGE TO WS-DETAIL
+ PERFORM CHECK
+ END-PERFORM.
+
+*> ===================================================================
+*> Single lookup
+*> ===================================================================
+T-LOOKUP-SUCCESS.
+ PERFORM FRESH-CONFIG
+ MOVE "01001-000" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK
+ AND ADDR-LOCALIDADE = "São Paulo"
+ AND ADDR-UF = "SP"
+ AND ADDR-LOGRADOURO = "Praça da Sé"
+ AND ADDR-IBGE = "3550308"
+ AND ADDR-CEP-MASKED = "01001-000"
+ AND ADDR-GEO-ABSENT
+ AND ADDR-NOT-TRUNCATED
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "lookup returns the address, masked cep, no geo" TO WS-LABEL
+ MOVE CEPIS-STATUS-MESSAGE TO WS-DETAIL
+ PERFORM CHECK.
+
+T-GEO-PRECISE.
+ PERFORM FRESH-CONFIG
+ MOVE "01310100" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND ADDR-GEO-KNOWN AND ADDR-GEO-PRECISE
+ AND ADDR-GEO-RAIO-M = 120
+ AND ADDR-GEO-LAT < -23.5 AND ADDR-GEO-LAT > -23.6
+ AND ADDR-GEO-LON < -46.6 AND ADDR-GEO-LON > -46.7
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "geo present is read and reports precisao logradouro"
+ TO WS-LABEL
+ PERFORM CHECK.
+
+T-GEO-CITY-WIDE.
+ PERFORM FRESH-CONFIG
+ MOVE "01001001" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND ADDR-GEO-KNOWN AND ADDR-GEO-CITY-WIDE
+ AND NOT ADDR-GEO-PRECISE AND ADDR-GEO-RAIO-M = 22000
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "a municipality centroid is not precise" TO WS-LABEL
+ PERFORM CHECK.
+
+T-GEO-MALFORMED.
+ PERFORM FRESH-CONFIG
+ MOVE "01001002" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND ADDR-GEO-ABSENT
+ AND ADDR-LOCALIDADE = "São Paulo"
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "a malformed geo does not lose the address" TO WS-LABEL
+ PERFORM CHECK.
+
+T-UNKNOWN-FIELDS.
+ PERFORM FRESH-CONFIG
+ MOVE "01001003" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND ADDR-LOCALIDADE = "São Paulo"
+ AND ADDR-GEO-ABSENT
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "fields the client does not know are ignored" TO WS-LABEL
+ MOVE CEPIS-STATUS-MESSAGE TO WS-DETAIL
+ PERFORM CHECK.
+
+T-LONG-VALUE-FLAGGED.
+ PERFORM FRESH-CONFIG
+ MOVE "01001010" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND ADDR-WAS-TRUNCATED
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "a value past the field width sets ADDR-TRUNCATED"
+ TO WS-LABEL
+ PERFORM CHECK.
+
+T-NOT-FOUND.
+ PERFORM FRESH-CONFIG
+ MOVE "99999999" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-NOT-FOUND AND ADDR-LOCALIDADE = SPACES
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "a missing CEP is CEPIS-NOT-FOUND" TO WS-LABEL
+ MOVE CEPIS-STATUS-MESSAGE TO WS-DETAIL
+ PERFORM CHECK.
+
+T-NOT-FOUND-NOT-RETRIED.
+ MOVE "/99999999" TO WS-COUNT-PATH
+ PERFORM COUNT-FOR
+ MOVE "N" TO WS-OK
+ IF WS-COUNT = 1
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "404 is definitive and is not retried" TO WS-LABEL
+ MOVE SPACES TO WS-DETAIL
+ STRING "requests: " WS-COUNT
+ DELIMITED BY SIZE INTO WS-DETAIL
+ END-STRING
+ PERFORM CHECK.
+
+T-INVALID-NEVER-LEAVES.
+ PERFORM FRESH-CONFIG
+ MOVE "123" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-INVALID-CEP
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "/123" TO WS-COUNT-PATH
+ PERFORM COUNT-FOR
+ IF WS-COUNT NOT = 0
+ MOVE "N" TO WS-OK
+ END-IF
+ MOVE "a malformed CEP never leaves the machine" TO WS-LABEL
+ PERFORM CHECK.
+
+T-API-400.
+ PERFORM FRESH-CONFIG
+ MOVE "00000000" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE 0 TO WS-TALLY
+ INSPECT CEPIS-STATUS-MESSAGE
+ TALLYING WS-TALLY FOR ALL "rejected by the API"
+ MOVE "N" TO WS-OK
+ IF CEPIS-INVALID-CEP AND WS-TALLY = 1
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "an API 400 carries the API's own message" TO WS-LABEL
+ MOVE CEPIS-STATUS-MESSAGE TO WS-DETAIL
+ PERFORM CHECK.
+
+T-NOT-JSON.
+ PERFORM FRESH-CONFIG
+ MOVE "01001004" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-INVALID-RESPONSE
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "a body that is not JSON is CEPIS-INVALID-RESPONSE"
+ TO WS-LABEL
+ MOVE CEPIS-STATUS-MESSAGE TO WS-DETAIL
+ PERFORM CHECK.
+
+*> ===================================================================
+*> Retrying
+*> ===================================================================
+T-RETRY-THEN-SUCCESS.
+ PERFORM FRESH-CONFIG
+ MOVE "01001005" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND ADDR-LOCALIDADE = "São Paulo"
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "/01001005" TO WS-COUNT-PATH
+ PERFORM COUNT-FOR
+ IF WS-COUNT NOT = 2
+ MOVE "N" TO WS-OK
+ END-IF
+ MOVE "500 is retried and the second attempt succeeds" TO WS-LABEL
+ MOVE SPACES TO WS-DETAIL
+ STRING "requests: " WS-COUNT
+ DELIMITED BY SIZE INTO WS-DETAIL
+ END-STRING
+ PERFORM CHECK.
+
+T-RETRY-EXHAUSTED.
+ PERFORM FRESH-CONFIG
+ MOVE "01001006" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-SERVER-ERROR AND CEPIS-STATUS-HTTP = 503
+ AND CEPIS-STATUS-ATTEMPTS = 3
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "/01001006" TO WS-COUNT-PATH
+ PERFORM COUNT-FOR
+ IF WS-COUNT NOT = 3
+ MOVE "N" TO WS-OK
+ END-IF
+ MOVE "three attempts, then CEPIS-SERVER-ERROR" TO WS-LABEL
+ MOVE SPACES TO WS-DETAIL
+ STRING "requests: " WS-COUNT
+ DELIMITED BY SIZE INTO WS-DETAIL
+ END-STRING
+ PERFORM CHECK.
+
+T-ONE-ATTEMPT-NO-RETRY.
+ PERFORM FRESH-CONFIG
+ MOVE 1 TO CFG-MAX-ATTEMPTS
+ MOVE "01001012" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-SERVER-ERROR
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "/01001012" TO WS-COUNT-PATH
+ PERFORM COUNT-FOR
+ IF WS-COUNT NOT = 1
+ MOVE "N" TO WS-OK
+ END-IF
+ MOVE "CFG-MAX-ATTEMPTS of 1 disables retrying" TO WS-LABEL
+ PERFORM CHECK.
+
+T-RATE-LIMITED.
+ PERFORM FRESH-CONFIG
+ MOVE "01001007" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-RATE-LIMITED AND CEPIS-STATUS-RETRY-AFTER-MS = 7000
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "429 exposes Retry-After in seconds" TO WS-LABEL
+ MOVE SPACES TO WS-DETAIL
+ STRING "retry-after ms: " CEPIS-STATUS-RETRY-AFTER-MS
+ DELIMITED BY SIZE INTO WS-DETAIL
+ END-STRING
+ PERFORM CHECK.
+
+T-RETRY-AFTER-CAPPED.
+ PERFORM FRESH-CONFIG
+ MOVE "01001008" TO CEPIS-CEP-INPUT
+ CALL "cepis_monotonic_ms" USING WS-T0
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ CALL "cepis_monotonic_ms" USING WS-T1
+ COMPUTE WS-ELAPSED = WS-T1 - WS-T0
+ MOVE "N" TO WS-OK
+ IF CEPIS-RATE-LIMITED AND WS-ELAPSED < 3000
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "an absurd Retry-After is capped at the backoff maximum"
+ TO WS-LABEL
+ MOVE SPACES TO WS-DETAIL
+ STRING "elapsed ms: " WS-ELAPSED
+ DELIMITED BY SIZE INTO WS-DETAIL
+ END-STRING
+ PERFORM CHECK.
+
+T-RETRY-AFTER-HTTP-DATE.
+ PERFORM FRESH-CONFIG
+ MOVE "01001009" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-RATE-LIMITED
+ AND CEPIS-STATUS-RETRY-AFTER-MS >= 1000
+ AND CEPIS-STATUS-RETRY-AFTER-MS <= 8000
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "Retry-After is also read as an HTTP date" TO WS-LABEL
+ MOVE SPACES TO WS-DETAIL
+ STRING "retry-after ms: " CEPIS-STATUS-RETRY-AFTER-MS
+ DELIMITED BY SIZE INTO WS-DETAIL
+ END-STRING
+ PERFORM CHECK.
+
+T-NETWORK-ERROR.
+ PERFORM FRESH-CONFIG
+ MOVE "http://127.0.0.1:1" TO CFG-BASE-URL
+ MOVE 2 TO CFG-MAX-ATTEMPTS
+ MOVE 1000 TO CFG-TIMEOUT-MS
+ MOVE "01001000" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-NETWORK-ERROR AND CEPIS-STATUS-ATTEMPTS = 2
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "a refused connection is CEPIS-NETWORK-ERROR" TO WS-LABEL
+ MOVE CEPIS-STATUS-MESSAGE TO WS-DETAIL
+ PERFORM CHECK.
+
+T-TOTAL-TIMEOUT.
+ PERFORM FRESH-CONFIG
+ MOVE 50 TO CFG-MAX-ATTEMPTS
+ MOVE 50 TO CFG-BACKOFF-BASE-MS
+ MOVE 50 TO CFG-BACKOFF-MAX-MS
+ MOVE 300 TO CFG-TOTAL-TIMEOUT-MS
+ MOVE "01001011" TO CEPIS-CEP-INPUT
+ CALL "cepis_monotonic_ms" USING WS-T0
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ CALL "cepis_monotonic_ms" USING WS-T1
+ COMPUTE WS-ELAPSED = WS-T1 - WS-T0
+ MOVE CEPIS-STATUS-ATTEMPTS TO WS-SAVED-ATTEMPTS
+ MOVE "N" TO WS-OK
+ IF CEPIS-SERVER-ERROR AND WS-ELAPSED < 3000
+ AND WS-SAVED-ATTEMPTS < 50
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "CFG-TOTAL-TIMEOUT-MS bounds the whole call" TO WS-LABEL
+ MOVE SPACES TO WS-DETAIL
+ STRING "elapsed ms: " WS-ELAPSED
+ " attempts: " WS-SAVED-ATTEMPTS
+ DELIMITED BY SIZE INTO WS-DETAIL
+ END-STRING
+ PERFORM CHECK.
+
+*> ===================================================================
+*> Batch
+*> ===================================================================
+T-BATCH-ORDER.
+ PERFORM FRESH-CONFIG
+ MOVE 3 TO BATCH-COUNT
+ MOVE "01001000" TO BATCH-REQUESTED (1)
+ MOVE "99999999" TO BATCH-REQUESTED (2)
+ MOVE "20040020" TO BATCH-REQUESTED (3)
+ CALL "CEPIS-LOOKUP-MANY" USING CEPIS-CONFIG CEPIS-BATCH
+ CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK
+ AND BATCH-ENTRY-FOUND (1)
+ AND BAT-LOCALIDADE (1) = "São Paulo"
+ AND BATCH-ENTRY-MISSING (2)
+ AND BATCH-ENTRY-FOUND (3)
+ AND BAT-UF (3) = "RJ"
+ AND BAT-GEO-ABSENT (1)
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "a missing CEP keeps its slot and nothing shifts" TO WS-LABEL
+ MOVE CEPIS-STATUS-MESSAGE TO WS-DETAIL
+ PERFORM CHECK.
+
+T-BATCH-QUOTING.
+ PERFORM FRESH-CONFIG
+ MOVE 1 TO BATCH-COUNT
+ MOVE "01001111" TO BATCH-REQUESTED (1)
+ CALL "CEPIS-LOOKUP-MANY" USING CEPIS-CONFIG CEPIS-BATCH
+ CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND BATCH-ENTRY-FOUND (1)
+ AND BAT-LOGRADOURO (1) = "Rua Um, Dois"
+ AND BAT-COMPLEMENTO (1) = 'aspas "aqui"'
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "quoted CSV fields with commas and quotes are parsed"
+ TO WS-LABEL
+ MOVE BAT-LOGRADOURO (1) TO WS-DETAIL
+ PERFORM CHECK.
+
+T-BATCH-MALFORMED-ENTRY.
+ PERFORM FRESH-CONFIG
+ MOVE 3 TO BATCH-COUNT
+ MOVE "01001000" TO BATCH-REQUESTED (1)
+ MOVE "nonsense" TO BATCH-REQUESTED (2)
+ MOVE "20040020" TO BATCH-REQUESTED (3)
+ CALL "CEPIS-LOOKUP-MANY" USING CEPIS-CONFIG CEPIS-BATCH
+ CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK
+ AND BATCH-ENTRY-FOUND (1)
+ AND BATCH-ENTRY-MISSING (2)
+ AND BATCH-ENTRY-FOUND (3)
+ AND BAT-UF (3) = "RJ"
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "a malformed CEP is missing, not an error" TO WS-LABEL
+ PERFORM CHECK.
+
+T-BATCH-EMPTY.
+ MOVE "/csv" TO WS-COUNT-PATH
+ PERFORM COUNT-FOR
+ MOVE WS-COUNT TO WS-COUNT-BEFORE
+ PERFORM FRESH-CONFIG
+ MOVE 0 TO BATCH-COUNT
+ CALL "CEPIS-LOOKUP-MANY" USING CEPIS-CONFIG CEPIS-BATCH
+ CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "/csv" TO WS-COUNT-PATH
+ PERFORM COUNT-FOR
+ IF WS-COUNT NOT = WS-COUNT-BEFORE
+ MOVE "N" TO WS-OK
+ END-IF
+ MOVE "an empty batch makes no request" TO WS-LABEL
+ PERFORM CHECK.
+
+T-BATCH-TRUNCATED.
+ PERFORM FRESH-CONFIG
+ MOVE 2 TO BATCH-COUNT
+ MOVE "01001000" TO BATCH-REQUESTED (1)
+ MOVE "00000099" TO BATCH-REQUESTED (2)
+ CALL "CEPIS-LOOKUP-MANY" USING CEPIS-CONFIG CEPIS-BATCH
+ CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND BATCH-ENTRY-FOUND (1)
+ AND BATCH-ENTRY-MISSING (2)
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "a short response pads rather than shifts" TO WS-LABEL
+ PERFORM CHECK.
+
+T-BATCH-CHUNKING.
+ MOVE "/csv" TO WS-COUNT-PATH
+ PERFORM COUNT-FOR
+ MOVE WS-COUNT TO WS-COUNT-BEFORE
+ PERFORM FRESH-CONFIG
+ MOVE 501 TO BATCH-COUNT
+ PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 501
+ COMPUTE WS-NUM8 = 70000000 + WS-I
+ MOVE WS-NUM8 TO BATCH-REQUESTED (WS-I)
+ END-PERFORM
+ CALL "CEPIS-LOOKUP-MANY" USING CEPIS-CONFIG CEPIS-BATCH
+ CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND BATCH-ENTRY-FOUND (1)
+ AND BATCH-ENTRY-FOUND (500)
+ AND BATCH-ENTRY-FOUND (501)
+ AND BAT-LOCALIDADE (501) = "Cidade Sintetica"
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "/csv" TO WS-COUNT-PATH
+ PERFORM COUNT-FOR
+ COMPUTE WS-COUNT = WS-COUNT - WS-COUNT-BEFORE
+ IF WS-COUNT NOT = 2
+ MOVE "N" TO WS-OK
+ END-IF
+ MOVE "501 CEPs become two requests, not one 400" TO WS-LABEL
+ MOVE SPACES TO WS-DETAIL
+ STRING "requests: " WS-COUNT " status: " CEPIS-STATUS-MESSAGE
+ DELIMITED BY SIZE INTO WS-DETAIL
+ END-STRING
+ PERFORM CHECK.
+
+T-BATCH-OVER-CAPACITY.
+ PERFORM FRESH-CONFIG
+ MOVE 1001 TO BATCH-COUNT
+ CALL "CEPIS-LOOKUP-MANY" USING CEPIS-CONFIG CEPIS-BATCH
+ CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-INVALID-CEP
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE 0 TO BATCH-COUNT
+ MOVE "a BATCH-COUNT past the table is refused, not truncated"
+ TO WS-LABEL
+ MOVE CEPIS-STATUS-MESSAGE TO WS-DETAIL
+ PERFORM CHECK.
+
+*> ===================================================================
+*> Health and configuration
+*> ===================================================================
+T-HEALTH.
+ PERFORM FRESH-CONFIG
+ CALL "CEPIS-HEALTH" USING CEPIS-CONFIG CEPIS-HEALTH CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND HEALTH-OK AND HEALTH-DNE = "26071"
+ AND HEALTH-CEPS = 1596168 AND HEALTH-GEO = 894489
+ AND HEALTH-BASE-GERADA-EM = "2026-07-24T03:48:28Z"
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "health reports the live database" TO WS-LABEL
+ MOVE CEPIS-STATUS-MESSAGE TO WS-DETAIL
+ PERFORM CHECK.
+
+T-HEALTH-DEGRADED.
+ PERFORM FRESH-CONFIG
+ MOVE SPACES TO CFG-BASE-URL
+ STRING FUNCTION TRIM (WS-BASE) "/degraded"
+ DELIMITED BY SIZE INTO CFG-BASE-URL
+ END-STRING
+ CALL "CEPIS-HEALTH" USING CEPIS-CONFIG CEPIS-HEALTH CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND NOT HEALTH-OK
+ AND HEALTH-STATUS = "degradado"
+ AND CEPIS-STATUS-ATTEMPTS = 1
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "a degraded API is diagnosed, not retried" TO WS-LABEL
+ MOVE HEALTH-STATUS TO WS-DETAIL
+ PERFORM CHECK.
+
+T-TRAILING-SLASH.
+ PERFORM FRESH-CONFIG
+ MOVE SPACES TO CFG-BASE-URL
+ STRING FUNCTION TRIM (WS-BASE) "/"
+ DELIMITED BY SIZE INTO CFG-BASE-URL
+ END-STRING
+ MOVE "01001000" TO CEPIS-CEP-INPUT
+ CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
+ CEPIS-ADDRESS CEPIS-STATUS
+ END-CALL
+ MOVE "N" TO WS-OK
+ IF CEPIS-OK AND ADDR-UF = "SP"
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "a trailing slash on the base URL is dropped" TO WS-LABEL
+ MOVE CEPIS-STATUS-MESSAGE TO WS-DETAIL
+ PERFORM CHECK.
+
+T-DEFAULT-USER-AGENT.
+ CALL "CEPIS-DEFAULT-CONFIG" USING CEPIS-CONFIG
+ MOVE 0 TO WS-TALLY
+ INSPECT CFG-USER-AGENT TALLYING WS-TALLY FOR ALL "cepis-cobol/0.1.0"
+ MOVE "N" TO WS-OK
+ IF WS-TALLY = 1 AND CFG-BASE-URL = "https://cep.is"
+ AND CFG-MAX-ATTEMPTS = 3 AND CFG-BACKOFF-BASE-MS = 500
+ AND CFG-BACKOFF-MAX-MS = 8000
+ MOVE "Y" TO WS-OK
+ END-IF
+ MOVE "the defaults identify the library and its version"
+ TO WS-LABEL
+ MOVE CFG-USER-AGENT TO WS-DETAIL
+ PERFORM CHECK.
+
+END PROGRAM CEPIS-TESTS.