diff --git a/src/main/java/dev/sorn/fmp4j/clients/FmpBulkClient.java b/src/main/java/dev/sorn/fmp4j/clients/FmpBulkClient.java index e791861..57a1313 100644 --- a/src/main/java/dev/sorn/fmp4j/clients/FmpBulkClient.java +++ b/src/main/java/dev/sorn/fmp4j/clients/FmpBulkClient.java @@ -3,8 +3,10 @@ import dev.sorn.fmp4j.cfg.FmpConfig; import dev.sorn.fmp4j.http.FmpHttpClient; import dev.sorn.fmp4j.models.FmpBalanceSheetStatement; +import dev.sorn.fmp4j.models.FmpCashFlowStatement; import dev.sorn.fmp4j.models.FmpCompanies; import dev.sorn.fmp4j.services.FmpBulkBalanceSheetStatementService; +import dev.sorn.fmp4j.services.FmpBulkCashFlowStatementService; import dev.sorn.fmp4j.services.FmpBulkCompaniesService; import dev.sorn.fmp4j.services.FmpService; import dev.sorn.fmp4j.types.FmpPart; @@ -16,10 +18,12 @@ public class FmpBulkClient { // Alphabetical order protected final FmpService fmpBulkCompaniesService; protected final FmpService fmpBulkBalanceSheetService; + protected final FmpService fmpBulkCashFlowService; public FmpBulkClient(FmpConfig fmpConfig, FmpHttpClient fmpHttpClient) { this.fmpBulkCompaniesService = new FmpBulkCompaniesService(fmpConfig, fmpHttpClient); this.fmpBulkBalanceSheetService = new FmpBulkBalanceSheetStatementService(fmpConfig, fmpHttpClient); + this.fmpBulkCashFlowService = new FmpBulkCashFlowStatementService(fmpConfig, fmpHttpClient); } public synchronized FmpCompanies[] companies(FmpPart part) { @@ -32,4 +36,11 @@ public synchronized FmpBalanceSheetStatement[] balanceSheetStatements(FmpYear ye fmpBulkBalanceSheetService.param("period", period); return fmpBulkBalanceSheetService.download(); } + + public synchronized FmpCashFlowStatement[] cashFlowStatements(FmpYear year, FmpPeriod period) { + fmpBulkCashFlowService.param("year", year); + fmpBulkCashFlowService.param("period", period); + + return fmpBulkCashFlowService.download(); + } } diff --git a/src/main/java/dev/sorn/fmp4j/services/FmpBulkCashFlowStatementService.java b/src/main/java/dev/sorn/fmp4j/services/FmpBulkCashFlowStatementService.java new file mode 100644 index 0000000..1e7cd3b --- /dev/null +++ b/src/main/java/dev/sorn/fmp4j/services/FmpBulkCashFlowStatementService.java @@ -0,0 +1,36 @@ +package dev.sorn.fmp4j.services; + +import static dev.sorn.fmp4j.json.FmpJsonUtils.typeRef; + +import dev.sorn.fmp4j.cfg.FmpConfig; +import dev.sorn.fmp4j.http.FmpHttpClient; +import dev.sorn.fmp4j.models.FmpCashFlowStatement; +import dev.sorn.fmp4j.types.FmpPeriod; +import dev.sorn.fmp4j.types.FmpYear; +import java.util.Map; + +public class FmpBulkCashFlowStatementService extends FmpService { + public FmpBulkCashFlowStatementService(FmpConfig cfg, FmpHttpClient http) { + super(cfg, http, typeRef(FmpCashFlowStatement[].class)); + } + + @Override + protected String relativeUrl() { + return "/cash-flow-statement-bulk"; + } + + @Override + protected Map> requiredParams() { + return Map.of("year", FmpYear.class, "period", FmpPeriod.class); + } + + @Override + protected Map> optionalParams() { + return Map.of(); + } + + @Override + protected Map headers() { + return Map.of("Content-Type", "text/csv"); + } +} diff --git a/src/test/java/dev/sorn/fmp4j/FmpClientTest.java b/src/test/java/dev/sorn/fmp4j/FmpClientTest.java index 8ad7cf9..b56161b 100644 --- a/src/test/java/dev/sorn/fmp4j/FmpClientTest.java +++ b/src/test/java/dev/sorn/fmp4j/FmpClientTest.java @@ -735,6 +735,27 @@ void cashFlowStatements(String p) { assertValidResult(result, limit.value(), FmpCashFlowStatement.class); } + @ParameterizedTest + @ValueSource(strings = {"annual"}) + void cashFlowStatementsBulk(String p) { + // given + var period = period(p); + var year = year("2023"); + var typeRef = typeRef(FmpCashFlowStatement[].class); + var endpoint = "cash-flow-statement-bulk"; + var uri = buildUri(endpoint); + var headers = Map.of("Content-Type", "text/csv"); + var params = buildParams(Map.of("year", year, "period", period)); + var file = String.format("stable/%s/?year=%s&period=%s.csv", endpoint, year, period); + + // when + mockHttpGet(uri, headers, params, file, typeRef); + var result = fmpClient.bulk().cashFlowStatements(year, period); + + // then + assertValidResult(result, 2, FmpCashFlowStatement.class); + } + @Test void cashFlowStatementTtm() { // given diff --git a/src/test/java/dev/sorn/fmp4j/services/FmpBulkCashFlowStatementServiceTest.java b/src/test/java/dev/sorn/fmp4j/services/FmpBulkCashFlowStatementServiceTest.java new file mode 100644 index 0000000..1ea4612 --- /dev/null +++ b/src/test/java/dev/sorn/fmp4j/services/FmpBulkCashFlowStatementServiceTest.java @@ -0,0 +1,78 @@ +package dev.sorn.fmp4j.services; + +import static dev.sorn.fmp4j.HttpClientStub.httpClientStub; +import static dev.sorn.fmp4j.TestUtils.assertAllFieldsNonNull; +import static dev.sorn.fmp4j.TestUtils.testResource; +import static dev.sorn.fmp4j.csv.FmpCsvDeserializer.FMP_CSV_DESERIALIZER; +import static dev.sorn.fmp4j.json.FmpJsonDeserializer.FMP_JSON_DESERIALIZER; +import static dev.sorn.fmp4j.types.FmpPeriod.period; +import static dev.sorn.fmp4j.types.FmpYear.year; +import static java.util.stream.IntStream.range; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dev.sorn.fmp4j.HttpClientStub; +import dev.sorn.fmp4j.cfg.FmpConfigImpl; +import dev.sorn.fmp4j.http.FmpHttpClient; +import dev.sorn.fmp4j.http.FmpHttpClientImpl; +import dev.sorn.fmp4j.models.FmpCashFlowStatement; +import dev.sorn.fmp4j.types.FmpPeriod; +import dev.sorn.fmp4j.types.FmpYear; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class FmpBulkCashFlowStatementServiceTest { + private final HttpClientStub httpStub = httpClientStub(); + private final FmpHttpClient http = new FmpHttpClientImpl(httpStub, FMP_JSON_DESERIALIZER, FMP_CSV_DESERIALIZER); + private final FmpService service = + new FmpBulkCashFlowStatementService(new FmpConfigImpl(), http); + + @Test + void relative_url() { + // given // when + var relativeUrl = service.relativeUrl(); + + // then + assertEquals("/cash-flow-statement-bulk", relativeUrl); + } + + @Test + void required_params() { + // given // when + var params = service.requiredParams(); + + // then + assertEquals(Map.of("year", FmpYear.class, "period", FmpPeriod.class), params); + } + + @Test + void optional_params() { + // given // when + var params = service.optionalParams(); + + // then + assertEquals(Map.of(), params); + } + + @ParameterizedTest + @ValueSource(strings = {"annual"}) + void successful_download_with_required_params(String periodString) { + // given + var year = year("2023"); + var period = period(periodString); + service.param("year", year); + service.param("period", period); + httpStub.configureResponse() + .body(testResource("stable/cash-flow-statement-bulk/?year=%s&period=%s.csv", year, period)) + .statusCode(200) + .apply(); + + // when + var result = service.download(); + + // then + assertEquals(2, result.length); + range(0, result.length).forEach(i -> assertAllFieldsNonNull(result[i])); + } +} diff --git a/src/testFixtures/resources/stable/cash-flow-statement-bulk/%3Fyear=2023&period=annual.csv b/src/testFixtures/resources/stable/cash-flow-statement-bulk/%3Fyear=2023&period=annual.csv new file mode 100644 index 0000000..1ba288f --- /dev/null +++ b/src/testFixtures/resources/stable/cash-flow-statement-bulk/%3Fyear=2023&period=annual.csv @@ -0,0 +1,3 @@ +date,symbol,reportedCurrency,cik,filingDate,acceptedDate,fiscalYear,period,netIncome,depreciationAndAmortization,deferredIncomeTax,stockBasedCompensation,changeInWorkingCapital,accountsReceivables,inventory,accountsPayables,otherWorkingCapital,otherNonCashItems,netCashProvidedByOperatingActivities,investmentsInPropertyPlantAndEquipment,acquisitionsNet,purchasesOfInvestments,salesMaturitiesOfInvestments,otherInvestingActivities,netCashProvidedByInvestingActivities,netDebtIssuance,longTermNetDebtIssuance,shortTermNetDebtIssuance,netStockIssuance,netCommonStockIssuance,commonStockIssuance,commonStockRepurchased,netPreferredStockIssuance,netDividendsPaid,commonDividendsPaid,preferredDividendsPaid,otherFinancingActivities,netCashProvidedByFinancingActivities,effectOfForexChangesOnCash,netChangeInCash,cashAtEndOfPeriod,cashAtBeginningOfPeriod,operatingCashFlow,capitalExpenditure,freeCashFlow,incomeTaxesPaid,interestPaid +"2025-03-31","000001.SZ","CNY","0000000000","2025-03-31","2025-03-31 00:00:00","2025","Q1","0","0","0","0","0","0","0","0","0","162946000000","162946000000","-338000000","0","-227916000000","253172000000","25000000","24943000000","0","0","0","0","0","0","0","0","-2538000000","-2538000000","0","-155860000000","-158398000000","-130000000","29361000000","286307000000","256946000000","162946000000","-338000000","162608000000","0","0", +"2025-02-20","000001.SZ","CNY","0000000000","2025-02-20","2025-02-31 00:00:00","2025","Q1","0","0","0","0","0","0","0","0","0","162946000000","162946000000","-338000000","0","-227916000000","253172000000","25000000","24943000000","0","0","0","0","0","0","0","0","-2538000000","-2538000000","0","-155860000000","-158398000000","-130000000","29361000000","286307000000","256946000000","162946000000","-338000000","162608000000","0","0" \ No newline at end of file