{ Test suite for the Cepis unit. Every test brings up a real HTTP server on loopback and points BaseURL at it. No test touches the network, and nothing about the HTTP layer is mocked: the code path exercised is the one that runs in production, request construction and error body reading included. It doubles as the test that BaseURL works - if it did not, none of this would run. The fixture field names stay Portuguese because they mirror the API's JSON. } program TestCepis; {$MODE OBJFPC} {$H+} uses {$IFDEF UNIX} cthreads, {$ENDIF} Classes, SysUtils, fpcunit, testregistry, consoletestrunner, fphttpserver, httpdefs, Cepis; const SeAddress = '{"cep":"01001000","logradouro":"Pra' + #$C3#$A7 + 'a da S' + #$C3#$A9 + '","complemento":"lado ' + #$C3#$AD + 'mpar","unidade":"","bairro":"S' + #$C3#$A9 + '","localidade":"S' + #$C3#$A3 + 'o Paulo","uf":"SP",' + '"estado":"S' + #$C3#$A3 + 'o Paulo","regiao":"Sudeste","ibge":"3550308",' + '"gia":"1004","ddd":"11","siafi":"7107"'; SaoPaulo = 'S' + #$C3#$A3 + 'o Paulo'; PracaDaSe = 'Pra' + #$C3#$A7 + 'a da S' + #$C3#$A9; CsvHeader = 'cep,logradouro,complemento,unidade,bairro,localidade,uf,' + 'estado,regiao,ibge,gia,ddd,siafi'; type TFakeReply = record Status: Integer; Body: string; HeaderName: string; HeaderValue: string; end; TFakeReplies = array of TFakeReply; TFakeHandler = procedure(const APath: string; ACallIndex: Integer; var AReply: TFakeReply) of object; TFakeRoute = class public Path: string; Replies: TFakeReplies; Cursor: Integer; end; TLocalHttpServer = class(TFPHttpServer) public property Address; end; TAcceptThread = class(TThread) private FServer: TLocalHttpServer; protected procedure Execute; override; public constructor Create(AServer: TLocalHttpServer); end; { A throwaway HTTP server on loopback with per-test programmable routes. } TFakeServer = class private FServer: TLocalHttpServer; FThread: TAcceptThread; FRoutes: TStringList; FCalls: TStringList; FLock: TRTLCriticalSection; FHandler: TFakeHandler; FPort: Word; procedure DoRequest(Sender: TObject; var ARequest: TFPHTTPConnectionRequest; var AResponse: TFPHTTPConnectionResponse); function GetBaseURL: string; function TryStart(APort: Word): Boolean; procedure Stop; function RouteFor(const APath: string): TFakeRoute; public constructor Create; destructor Destroy; override; procedure Respond(const APath: string; AStatus: Integer; const ABody: string); procedure RespondWithHeader(const APath: string; AStatus: Integer; const ABody, AHeaderName, AHeaderValue: string); procedure RespondSequence(const APath: string; const AReplies: array of TFakeReply); function CallCount(const APrefix: string): Integer; function TotalCalls: Integer; property Handler: TFakeHandler read FHandler write FHandler; property BaseURL: string read GetBaseURL; end; { Fixture: a fake server plus a client whose backoff is near zero. } TCepisTestCase = class(TTestCase) protected FServer: TFakeServer; FClient: TCepisClient; procedure SetUp; override; procedure TearDown; override; end; function Reply(AStatus: Integer; const ABody: string): TFakeReply; begin Result.Status := AStatus; Result.Body := ABody; Result.HeaderName := ''; Result.HeaderValue := ''; end; constructor TAcceptThread.Create(AServer: TLocalHttpServer); begin FServer := AServer; FreeOnTerminate := False; inherited Create(False); end; procedure TAcceptThread.Execute; begin try FServer.Active := True; except on E: Exception do ; end; end; constructor TFakeServer.Create; var Attempt: Integer; begin inherited Create; InitCriticalSection(FLock); FRoutes := TStringList.Create; FRoutes.OwnsObjects := True; FCalls := TStringList.Create; for Attempt := 1 to 40 do if TryStart(20000 + Random(30000)) then begin FCalls.Clear; Exit; end; raise Exception.Create('could not bind a loopback port for the test server'); end; function TFakeServer.TryStart(APort: Word): Boolean; var Probe: TCepisClient; Waited: Integer; Health: TCepisHealth; begin FPort := APort; FServer := TLocalHttpServer.Create(nil); FServer.Address := '127.0.0.1'; FServer.Port := APort; FServer.Threaded := True; FServer.AcceptIdleTimeout := 50; FServer.OnRequest := @DoRequest; FThread := TAcceptThread.Create(FServer); Probe := TCepisClient.Create; try Probe.BaseURL := GetBaseURL; Probe.MaxAttempts := 1; Probe.Timeout := 1.0; Waited := 0; while Waited < 3000 do begin if FThread.Finished then Break; try Health := Probe.Health; Health.Free; Exit(True); except on E: ECepisNetworkError do ; on E: ECepis do Exit(True); end; Sleep(20); Inc(Waited, 20); end; finally Probe.Free; end; Stop; Result := False; end; procedure TFakeServer.Stop; begin if FServer <> nil then try FServer.Active := False; except on E: Exception do ; end; if FThread <> nil then begin FThread.WaitFor; FreeAndNil(FThread); end; FreeAndNil(FServer); end; destructor TFakeServer.Destroy; begin Stop; FreeAndNil(FRoutes); FreeAndNil(FCalls); DoneCriticalSection(FLock); inherited Destroy; end; function TFakeServer.GetBaseURL: string; begin Result := Format('http://127.0.0.1:%d', [FPort]); end; function TFakeServer.RouteFor(const APath: string): TFakeRoute; var Index: Integer; begin Index := FRoutes.IndexOf(APath); if Index >= 0 then Exit(TFakeRoute(FRoutes.Objects[Index])); Result := TFakeRoute.Create; Result.Path := APath; Result.Cursor := 0; FRoutes.AddObject(APath, Result); end; procedure TFakeServer.Respond(const APath: string; AStatus: Integer; const ABody: string); begin RespondWithHeader(APath, AStatus, ABody, '', ''); end; procedure TFakeServer.RespondWithHeader(const APath: string; AStatus: Integer; const ABody, AHeaderName, AHeaderValue: string); var Route: TFakeRoute; begin EnterCriticalSection(FLock); try Route := RouteFor(APath); SetLength(Route.Replies, 1); Route.Replies[0].Status := AStatus; Route.Replies[0].Body := ABody; Route.Replies[0].HeaderName := AHeaderName; Route.Replies[0].HeaderValue := AHeaderValue; Route.Cursor := 0; finally LeaveCriticalSection(FLock); end; end; procedure TFakeServer.RespondSequence(const APath: string; const AReplies: array of TFakeReply); var Route: TFakeRoute; I: Integer; begin EnterCriticalSection(FLock); try Route := RouteFor(APath); SetLength(Route.Replies, Length(AReplies)); for I := 0 to High(AReplies) do Route.Replies[I] := AReplies[I]; Route.Cursor := 0; finally LeaveCriticalSection(FLock); end; end; function TFakeServer.CallCount(const APrefix: string): Integer; var I: Integer; begin Result := 0; EnterCriticalSection(FLock); try for I := 0 to FCalls.Count - 1 do if Copy(FCalls[I], 1, Length(APrefix)) = APrefix then Inc(Result); finally LeaveCriticalSection(FLock); end; end; function TFakeServer.TotalCalls: Integer; begin EnterCriticalSection(FLock); try Result := FCalls.Count; finally LeaveCriticalSection(FLock); end; end; procedure TFakeServer.DoRequest(Sender: TObject; var ARequest: TFPHTTPConnectionRequest; var AResponse: TFPHTTPConnectionResponse); var Path: string; Current: TFakeReply; Route: TFakeRoute; Index, CallIndex: Integer; begin Path := ARequest.URL; Current := Reply(404, '{"erro":"cep_nao_encontrado"}'); EnterCriticalSection(FLock); try FCalls.Add(Path); CallIndex := FCalls.Count; Index := FRoutes.IndexOf(Path); if Index >= 0 then begin Route := TFakeRoute(FRoutes.Objects[Index]); if Length(Route.Replies) > 0 then begin Current := Route.Replies[Route.Cursor]; if Route.Cursor < High(Route.Replies) then Inc(Route.Cursor); end; end else if Assigned(FHandler) then begin Current := Reply(200, ''); FHandler(Path, CallIndex, Current); end; finally LeaveCriticalSection(FLock); end; AResponse.Code := Current.Status; AResponse.CodeText := 'Test'; AResponse.ContentType := 'application/json; charset=utf-8'; if Current.HeaderName <> '' then AResponse.SetCustomHeader(Current.HeaderName, Current.HeaderValue); AResponse.Content := Current.Body; end; procedure TCepisTestCase.SetUp; begin inherited SetUp; FServer := TFakeServer.Create; FClient := TCepisClient.Create; FClient.BaseURL := FServer.BaseURL; FClient.Timeout := 5.0; FClient.BackoffBase := 0.001; FClient.BackoffMax := 0.01; end; procedure TCepisTestCase.TearDown; begin FreeAndNil(FClient); FreeAndNil(FServer); inherited TearDown; end; type TTestNormalisation = class(TTestCase) published procedure AcceptsMaskedAndUnmasked; procedure RejectsBadFormat; procedure FormatsBack; end; procedure TTestNormalisation.AcceptsMaskedAndUnmasked; begin AssertEquals('01001000', CepisNormalizeCEP('01001000')); AssertEquals('01001000', CepisNormalizeCEP('01001-000')); AssertEquals('01001000', CepisNormalizeCEP(' 01001-000 ')); AssertEquals('01001000', CepisNormalizeCEP('01001.000')); end; procedure TTestNormalisation.RejectsBadFormat; const Bad: array[0..4] of string = ('', '123', '0100100a', '010010000', 'abcdefgh'); var I: Integer; Raised: Boolean; begin for I := Low(Bad) to High(Bad) do begin Raised := False; try CepisNormalizeCEP(Bad[I]); except on E: ECepisInvalidCEP do Raised := True; end; AssertTrue('expected ECepisInvalidCEP for "' + Bad[I] + '"', Raised); end; end; procedure TTestNormalisation.FormatsBack; var Address: TCepisAddress; begin Address := TCepisAddress.Create; try AssertEquals('', Address.FormattedCEP); finally Address.Free; end; end; type TTestLookup = class(TCepisTestCase) published procedure SuccessfulLookup; procedure ReadsTheCoordinateWhenPresent; procedure MunicipalityCoordinateIsNotPrecise; procedure MalformedGeoDoesNotLoseTheAddress; procedure NewApiFieldDoesNotBreak; procedure NotFound; procedure NotFoundIsNotRetried; procedure InvalidCepNeverLeavesTheMachine; procedure Api400BecomesInvalidCep; procedure ResponseThatIsNotJson; end; procedure TTestLookup.SuccessfulLookup; var Address: TCepisAddress; begin FServer.Respond('/01001000', 200, SeAddress + '}'); Address := FClient.Lookup('01001-000'); try AssertEquals(SaoPaulo, Address.Localidade); AssertEquals('SP', Address.UF); AssertEquals(PracaDaSe, Address.Logradouro); AssertEquals('01001-000', Address.FormattedCEP); AssertTrue('geo must be nil when the API omits it', Address.Geo = nil); finally Address.Free; end; end; procedure TTestLookup.ReadsTheCoordinateWhenPresent; var Address: TCepisAddress; begin FServer.Respond('/01310100', 200, SeAddress + ',"geo":{"lat":-23.5,"lon":-46.6,"raio_m":120,"precisao":"logradouro"}}'); Address := FClient.Lookup('01310100'); try AssertTrue('geo must be present', Address.Geo <> nil); AssertEquals(-23.5, Address.Geo.Lat, 0.0001); AssertEquals(-46.6, Address.Geo.Lon, 0.0001); AssertEquals(120, Address.Geo.RaioM); AssertTrue('street-level geo is precise', Address.Geo.IsPrecise); finally Address.Free; end; end; procedure TTestLookup.MunicipalityCoordinateIsNotPrecise; var Address: TCepisAddress; begin FServer.Respond('/01001000', 200, SeAddress + ',"geo":{"lat":-23.5,"lon":-46.6,"raio_m":22000,"precisao":"localidade"}}'); Address := FClient.Lookup('01001000'); try AssertTrue('geo must be present', Address.Geo <> nil); AssertFalse('municipality centroid is not precise', Address.Geo.IsPrecise); finally Address.Free; end; end; procedure TTestLookup.MalformedGeoDoesNotLoseTheAddress; var Address: TCepisAddress; begin FServer.Respond('/01001000', 200, SeAddress + ',"geo":{"lat":"not a number"}}'); Address := FClient.Lookup('01001000'); try AssertTrue('malformed geo is dropped', Address.Geo = nil); AssertEquals(SaoPaulo, Address.Localidade); finally Address.Free; end; end; procedure TTestLookup.NewApiFieldDoesNotBreak; var Address: TCepisAddress; begin FServer.Respond('/01001000', 200, SeAddress + ',"campo_do_futuro":"value"}'); Address := FClient.Lookup('01001000'); try AssertEquals(SaoPaulo, Address.Localidade); AssertEquals('value', Address.RawField('campo_do_futuro')); finally Address.Free; end; end; procedure TTestLookup.NotFound; var Address: TCepisAddress; Raised: Boolean; Message: string; begin FServer.Respond('/99999999', 404, '{"erro":"cep_nao_encontrado","mensagem":"gone"}'); Raised := False; Message := ''; Address := nil; try Address := FClient.Lookup('99999999'); except on E: ECepisNotFound do begin Raised := True; Message := E.Message; end; end; Address.Free; AssertTrue('expected ECepisNotFound', Raised); AssertTrue('message must name the CEP', Pos('99999999', Message) > 0); end; procedure TTestLookup.NotFoundIsNotRetried; var Address: TCepisAddress; Raised: Boolean; begin FServer.Respond('/99999999', 404, '{"erro":"cep_nao_encontrado"}'); Raised := False; Address := nil; try Address := FClient.Lookup('99999999'); except on E: ECepisNotFound do Raised := True; end; Address.Free; AssertTrue('expected ECepisNotFound', Raised); AssertEquals(1, FServer.CallCount('/99999999')); end; procedure TTestLookup.InvalidCepNeverLeavesTheMachine; var Address: TCepisAddress; Raised: Boolean; begin Raised := False; Address := nil; try Address := FClient.Lookup('123'); except on E: ECepisInvalidCEP do Raised := True; end; Address.Free; AssertTrue('expected ECepisInvalidCEP', Raised); AssertEquals(0, FServer.TotalCalls); end; procedure TTestLookup.Api400BecomesInvalidCep; var Address: TCepisAddress; Raised: Boolean; Message: string; begin FServer.Respond('/00000000', 400, '{"erro":"cep_invalido","mensagem":"rejected by the API"}'); Raised := False; Message := ''; Address := nil; try Address := FClient.Lookup('00000000'); except on E: ECepisInvalidCEP do begin Raised := True; Message := E.Message; end; end; Address.Free; AssertTrue('expected ECepisInvalidCEP', Raised); AssertEquals('rejected by the API', Message); AssertEquals(1, FServer.CallCount('/00000000')); end; procedure TTestLookup.ResponseThatIsNotJson; var Address: TCepisAddress; Raised: Boolean; begin FServer.Respond('/01001000', 200, 'captive portal'); Raised := False; Address := nil; try Address := FClient.Lookup('01001000'); except on E: ECepisInvalidResponse do Raised := True; end; Address.Free; AssertTrue('expected ECepisInvalidResponse', Raised); end; type TTestRetry = class(TCepisTestCase) published procedure RetriesOn500ThenSucceeds; procedure ExhaustsAttemptsThenRaises; procedure MaxAttemptsOneDisablesRetry; procedure RateLimitedCarriesRetryAfter; procedure RetryAfterHonoursTheCap; procedure RetryAfterAcceptsAnHttpDate; procedure NetworkErrorWhenNothingListens; procedure TotalTimeoutBoundsTheWholeCall; end; procedure TTestRetry.RetriesOn500ThenSucceeds; var Address: TCepisAddress; begin FServer.RespondSequence('/01001000', [Reply(500, '{"erro":"interno"}'), Reply(200, SeAddress + '}')]); Address := FClient.Lookup('01001000'); try AssertEquals(SaoPaulo, Address.Localidade); AssertEquals(2, FServer.CallCount('/01001000')); finally Address.Free; end; end; procedure TTestRetry.ExhaustsAttemptsThenRaises; var Address: TCepisAddress; Status: Integer; Raised: Boolean; begin FServer.Respond('/01001000', 503, '{"erro":"interno"}'); Raised := False; Status := 0; Address := nil; try Address := FClient.Lookup('01001000'); except on E: ECepisServerError do begin Raised := True; Status := E.Status; end; end; Address.Free; AssertTrue('expected ECepisServerError', Raised); AssertEquals(503, Status); AssertEquals(3, FServer.CallCount('/01001000')); end; procedure TTestRetry.MaxAttemptsOneDisablesRetry; var Address: TCepisAddress; Raised: Boolean; begin FClient.MaxAttempts := 1; FServer.Respond('/01001000', 500, '{}'); Raised := False; Address := nil; try Address := FClient.Lookup('01001000'); except on E: ECepisServerError do Raised := True; end; Address.Free; AssertTrue('expected ECepisServerError', Raised); AssertEquals(1, FServer.CallCount('/01001000')); end; procedure TTestRetry.RateLimitedCarriesRetryAfter; var Address: TCepisAddress; Raised: Boolean; RetryAfter: Double; begin FServer.RespondWithHeader('/01001000', 429, '{}', 'Retry-After', '7'); Raised := False; RetryAfter := -1; Address := nil; try Address := FClient.Lookup('01001000'); except on E: ECepisRateLimited do begin Raised := True; RetryAfter := E.RetryAfter; end; end; Address.Free; AssertTrue('expected ECepisRateLimited', Raised); AssertEquals(7.0, RetryAfter, 0.0001); end; procedure TTestRetry.RetryAfterHonoursTheCap; var Address: TCepisAddress; Raised: Boolean; Started: QWord; begin FServer.RespondWithHeader('/01001000', 429, '{}', 'Retry-After', '3600'); Raised := False; Address := nil; Started := GetTickCount64; try Address := FClient.Lookup('01001000'); except on E: ECepisRateLimited do Raised := True; end; Address.Free; AssertTrue('expected ECepisRateLimited', Raised); AssertTrue('an absurd Retry-After must not park the thread', GetTickCount64 - Started < 5000); end; procedure TTestRetry.RetryAfterAcceptsAnHttpDate; var Address: TCepisAddress; Raised: Boolean; Started: QWord; begin FServer.RespondWithHeader('/01001000', 429, '{}', 'Retry-After', 'Wed, 21 Oct 2026 07:28:00 GMT'); Raised := False; Address := nil; Started := GetTickCount64; try Address := FClient.Lookup('01001000'); except on E: ECepisRateLimited do Raised := True; end; Address.Free; AssertTrue('expected ECepisRateLimited', Raised); AssertTrue('a date far in the future is still capped', GetTickCount64 - Started < 5000); end; procedure TTestRetry.NetworkErrorWhenNothingListens; var Client: TCepisClient; Address: TCepisAddress; Raised: Boolean; begin Client := TCepisClient.Create; try Client.BaseURL := 'http://127.0.0.1:1'; Client.MaxAttempts := 2; Client.BackoffBase := 0.001; Client.BackoffMax := 0.01; Client.Timeout := 1.0; Raised := False; Address := nil; try Address := Client.Lookup('01001000'); except on E: ECepisNetworkError do Raised := True; end; Address.Free; AssertTrue('expected ECepisNetworkError', Raised); finally Client.Free; end; end; procedure TTestRetry.TotalTimeoutBoundsTheWholeCall; var Address: TCepisAddress; Raised: Boolean; Started: QWord; begin FClient.MaxAttempts := 50; FClient.BackoffBase := 0.05; FClient.BackoffMax := 0.05; FClient.TotalTimeout := 0.3; FServer.Respond('/01001000', 500, '{}'); Raised := False; Address := nil; Started := GetTickCount64; try Address := FClient.Lookup('01001000'); except on E: ECepisServerError do Raised := True; end; Address.Free; AssertTrue('expected ECepisServerError', Raised); AssertTrue('the deadline must cut the run short', GetTickCount64 - Started < 3000); AssertTrue('fewer than the 50 configured attempts', FServer.CallCount('/01001000') < 50); end; type TTestBatch = class(TCepisTestCase) private FChunkCalls: Integer; procedure ChunkHandler(const APath: string; ACallIndex: Integer; var AReply: TFakeReply); published procedure PreservesOrderAndMarksMissing; procedure FieldWithCommaIsParsed; procedure EmptyListMakesNoRequest; procedure BatchCarriesNoGeo; procedure ChunksAboveTheApiLimit; procedure TruncatedResponseDoesNotShift; end; procedure TTestBatch.PreservesOrderAndMarksMissing; var Result: TCepisAddressList; begin FServer.Respond('/csv?ceps=01001000%2C99999999%2C20040020', 200, CsvHeader + #10 + '01001000,' + PracaDaSe + ',lado ' + #$C3#$AD + 'mpar,,S' + #$C3#$A9 + ',' + SaoPaulo + ',SP,' + SaoPaulo + ',Sudeste,3550308,1004,11,7107' + #10 + '99999999,,,,,,,,,,,,' + #10 + '20040020,Avenida Rio Branco,,,Centro,Rio de Janeiro,RJ,Rio de Janeiro,' + 'Sudeste,3304557,,21,6001' + #10); Result := FClient.LookupMany(['01001000', '99999999', '20040020']); try AssertEquals(3, Result.Count); AssertTrue('first must be present', Result[0] <> nil); AssertEquals(SaoPaulo, Result[0].Localidade); AssertTrue('missing CEP becomes nil at its position', Result[1] = nil); AssertTrue('third must be present', Result[2] <> nil); AssertEquals('RJ', Result[2].UF); finally Result.Free; end; end; procedure TTestBatch.FieldWithCommaIsParsed; var Result: TCepisAddressList; begin FServer.Respond('/csv?ceps=01001000', 200, CsvHeader + #10 + '01001000,"Rua Um, Dois",,,Centro,' + SaoPaulo + ',SP,' + SaoPaulo + ',Sudeste,1,,11,1' + #10); Result := FClient.LookupMany(['01001000']); try AssertEquals(1, Result.Count); AssertTrue('must be present', Result[0] <> nil); AssertEquals('Rua Um, Dois', Result[0].Logradouro); finally Result.Free; end; end; procedure TTestBatch.EmptyListMakesNoRequest; var Result: TCepisAddressList; Empty: array of string; begin SetLength(Empty, 0); Result := FClient.LookupMany(Empty); try AssertEquals(0, Result.Count); AssertEquals(0, FServer.TotalCalls); finally Result.Free; end; end; procedure TTestBatch.BatchCarriesNoGeo; var Result: TCepisAddressList; begin FServer.Respond('/csv?ceps=01001000', 200, CsvHeader + #10 + '01001000,' + PracaDaSe + ',,,Centro,' + SaoPaulo + ',SP,' + SaoPaulo + ',Sudeste,1,,11,1' + #10); Result := FClient.LookupMany(['01001000']); try AssertTrue('must be present', Result[0] <> nil); AssertTrue('a batch never carries a coordinate', Result[0].Geo = nil); finally Result.Free; end; end; procedure TTestBatch.ChunkHandler(const APath: string; ACallIndex: Integer; var AReply: TFakeReply); var Body: string; I, First, Last: Integer; begin Inc(FChunkCalls); if FChunkCalls = 1 then begin First := 0; Last := 499; end else begin First := 500; Last := 500; end; Body := CsvHeader + #10; for I := First to Last do Body := Body + Format('%.8d,,,,,X,SP,%s,Sudeste,1,,11,1', [I, SaoPaulo]) + #10; AReply.Status := 200; AReply.Body := Body; end; procedure TTestBatch.ChunksAboveTheApiLimit; var Ceps: array of string; Result: TCepisAddressList; I: Integer; begin FChunkCalls := 0; FServer.Handler := @ChunkHandler; SetLength(Ceps, 501); for I := 0 to 500 do Ceps[I] := Format('%.8d', [I]); Result := FClient.LookupMany(Ceps); try AssertEquals(501, Result.Count); AssertEquals(2, FChunkCalls); AssertTrue('last entry present', Result[500] <> nil); finally Result.Free; end; end; procedure TTestBatch.TruncatedResponseDoesNotShift; var Result: TCepisAddressList; begin FServer.Respond('/csv?ceps=01001000%2C20040020', 200, CsvHeader + #10 + '01001000,,,,,' + SaoPaulo + ',SP,' + SaoPaulo + ',Sudeste,1,,11,1' + #10); Result := FClient.LookupMany(['01001000', '20040020']); try AssertEquals(2, Result.Count); AssertTrue('first must be present', Result[0] <> nil); AssertTrue('padding beats shifting', Result[1] = nil); finally Result.Free; end; end; type TTestHealth = class(TCepisTestCase) published procedure ReadsHealth; procedure DegradedDoesNotRaise; end; procedure TTestHealth.ReadsHealth; var Health: TCepisHealth; begin FServer.Respond('/health', 200, '{"status":"ok","dne":"26071","ceps":1596168,"geo":894489,' + '"base_gerada_em":"2026-07-24T03:48:28Z"}'); Health := FClient.Health; try AssertTrue('must report healthy', Health.Ok); AssertEquals('26071', Health.Dne); AssertEquals(1596168, Health.Ceps); AssertEquals(894489, Health.Geo); AssertEquals('2026-07-24T03:48:28Z', Health.BaseGeradaEm); finally Health.Free; end; end; procedure TTestHealth.DegradedDoesNotRaise; var Health: TCepisHealth; begin FServer.Respond('/health', 503, '{"status":"degradado","dne":"","ceps":0,"geo":0}'); Health := FClient.Health; try AssertFalse('degraded is reported, not raised', Health.Ok); AssertEquals('degradado', Health.Status); AssertEquals(1, FServer.CallCount('/health')); finally Health.Free; end; end; type TTestConfiguration = class(TTestCase) published procedure TrailingSlashIsNotDuplicated; procedure InvalidParameters; procedure UserAgentIdentifiesTheLibrary; procedure DefaultsAreTheDocumentedOnes; end; procedure TTestConfiguration.TrailingSlashIsNotDuplicated; var Client: TCepisClient; begin Client := TCepisClient.Create; try Client.BaseURL := 'http://x/'; AssertEquals('http://x', Client.BaseURL); finally Client.Free; end; end; procedure TTestConfiguration.InvalidParameters; var Client: TCepisClient; Raised: Boolean; begin Client := TCepisClient.Create; try Raised := False; try Client.MaxAttempts := 0; except on E: ECepis do Raised := True; end; AssertTrue('MaxAttempts = 0 must be rejected', Raised); Raised := False; try Client.Timeout := 0; except on E: ECepis do Raised := True; end; AssertTrue('Timeout = 0 must be rejected', Raised); finally Client.Free; end; end; procedure TTestConfiguration.UserAgentIdentifiesTheLibrary; var Client: TCepisClient; begin Client := TCepisClient.Create; try AssertTrue('User-Agent must name the library', Pos('cepis-pascal/', Client.UserAgent) = 1); finally Client.Free; end; end; procedure TTestConfiguration.DefaultsAreTheDocumentedOnes; var Client: TCepisClient; begin Client := TCepisClient.Create; try AssertEquals(CepisDefaultURL, Client.BaseURL); AssertEquals(3, Client.MaxAttempts); AssertEquals(0.5, Client.BackoffBase, 0.0001); AssertEquals(8.0, Client.BackoffMax, 0.0001); AssertEquals(500, CepisMaxBatch); finally Client.Free; end; end; type TTestOwnership = class(TCepisTestCase) published procedure ExtractKeepsAnAddressAlive; procedure ExtractGeoTransfersTheCoordinate; end; procedure TTestOwnership.ExtractKeepsAnAddressAlive; var List: TCepisAddressList; Kept: TCepisAddress; begin FServer.Respond('/csv?ceps=01001000', 200, CsvHeader + #10 + '01001000,' + PracaDaSe + ',,,Centro,' + SaoPaulo + ',SP,' + SaoPaulo + ',Sudeste,1,,11,1' + #10); List := FClient.LookupMany(['01001000']); Kept := List.Extract(0); try AssertTrue('list no longer owns it', List[0] = nil); List.Free; AssertEquals(SaoPaulo, Kept.Localidade); finally Kept.Free; end; end; procedure TTestOwnership.ExtractGeoTransfersTheCoordinate; var Address: TCepisAddress; Geo: TCepisGeo; begin FServer.Respond('/01310100', 200, SeAddress + ',"geo":{"lat":-23.5,"lon":-46.6,"raio_m":120,"precisao":"logradouro"}}'); Address := FClient.Lookup('01310100'); Geo := Address.ExtractGeo; try AssertTrue('address no longer owns it', Address.Geo = nil); Address.Free; AssertEquals(120, Geo.RaioM); finally Geo.Free; end; end; var Runner: TTestRunner; begin Randomize; RegisterTest(TTestNormalisation); RegisterTest(TTestLookup); RegisterTest(TTestRetry); RegisterTest(TTestBatch); RegisterTest(TTestHealth); RegisterTest(TTestConfiguration); RegisterTest(TTestOwnership); Runner := TTestRunner.Create(nil); try Runner.Initialize; Runner.Title := 'cepis test suite'; Runner.Run; finally Runner.Free; end; end.