Files
2026-05-27 23:29:53 +01:00

7.3 KiB

Capability Spec Delta: currency-provider-cryptocompare (currency-provider-cryptocompare)

Package: @suaveplan/currency-provider-cryptocompare Status: ARCHIVED

Every requirement below carries a **Verified by:** line at authoring time.

ADDED Requirements

Requirement: CryptoCompareProvider — public pricemulti rate fetch

The package SHALL export CryptoCompareProvider implementing CurrencyProvider from @suaveplan/currency with name === "cryptocompare", supportsCrypto === true, supportsFiat === false. fetchRates(options?) SHALL call GET <baseUrl>/pricemulti?fsyms=<targets>&tsyms=<base> via the shared httpRequest helper from @suaveplan/currency-providers, defaulting base to "USD" and targets to the 15-symbol built-in list when options.targets is omitted or empty. Returned rates SHALL be the inverted spot price (1 / price) for every symbol whose quote in the requested base is a finite positive number; non-finite and non-positive quotes SHALL be skipped.

Scenario: defaults base to USD and inverts pricemulti payload

  • WHEN fetchRates() is called against { BTC: { USD: 67000 }, ETH: { USD: 3500 }, FAKE: { USD: 0 } }
  • THEN result.base === "USD", result.rates.BTC ≈ 1 / 67000, result.rates.ETH ≈ 1 / 3500, and result.rates.FAKE is absent.

Scenario: honours custom baseUrl and explicit targets

  • WHEN fetchRates({ base: "EUR", targets: ["BTC", "ETH"] }) is called with baseUrl: "https://staging.example.com/data"
  • THEN the request URL is exactly https://staging.example.com/data/pricemulti?fsyms=BTC,ETH&tsyms=EUR.

Scenario: empty targets array falls back to built-in defaults

  • WHEN fetchRates({ base: "USD", targets: [] }) is called
  • THEN the request URL fsyms parameter starts with BTC,ETH,SOL,USDT.

Verified by: src/cryptocompare/cryptocompare.test.ts::"reports stable metadata identifying the provider" AND src/cryptocompare/cryptocompare.test.ts::"inverts pricemulti payload, skips zero prices, and defaults base to USD" AND src/cryptocompare/cryptocompare.test.ts::"honours custom baseUrl and explicit targets list" AND src/cryptocompare/cryptocompare.test.ts::"falls back to defaults when targets array is empty"


Requirement: Optional API key attached via Apikey authorization header

The package SHALL accept an optional apiKey field on the constructor config. When apiKey is provided and non-whitespace, every outbound request SHALL carry an authorization: Apikey <key> header per CryptoCompare's documented Apikey auth scheme. When apiKey is omitted, undefined, or whitespace-only, the request SHALL be issued with no authorization header (keyless free-tier mode).

Scenario: keyless construction issues no authorization header

  • WHEN new CryptoCompareProvider({ fetch }).fetchRates(...) runs
  • THEN the injected fetch sees init?.headers === undefined.

Scenario: keyed construction issues authorization: Apikey

  • WHEN new CryptoCompareProvider({ fetch, apiKey: "secret-key" }).fetchRates(...) runs
  • THEN the injected fetch sees init.headers.authorization === "Apikey secret-key".

Scenario: whitespace-only apiKey is treated as absent

  • WHEN new CryptoCompareProvider({ fetch, apiKey: " " }).fetchRates(...) runs
  • THEN the injected fetch sees init?.headers === undefined.

Verified by: src/cryptocompare/cryptocompare.test.ts::"sends authorization header only when an apiKey is provided" AND src/cryptocompare/cryptocompare.test.ts::"treats whitespace-only apiKey as absent and omits the header"


Requirement: Typed-error mapping for HTTP, parse, upstream, and unknown-currency failures

The package SHALL surface every failure as CurrencyProviderError from @suaveplan/currency-providers with providerName === "cryptocompare". Non-2xx responses SHALL raise HTTP_ERROR (via the shared httpRequest helper, populating status). Invalid JSON bodies and non-object parsed payloads SHALL raise PARSE_ERROR. Payloads carrying Response: "Error" SHALL raise UPSTREAM_ERROR with upstreamMessage set to the upstream Message field, or to "unknown upstream error" when Message is absent. fetchRates() SHALL raise UNKNOWN_CURRENCY when no requested symbol resolves to a finite positive quote in the configured base.

Scenario: non-2xx response raises HTTP_ERROR

  • WHEN the injected fetch returns HTTP 502
  • THEN the call rejects with code === "CURRENCY_PROVIDER.HTTP_ERROR" and status === 502.

Scenario: invalid JSON body raises PARSE_ERROR

  • WHEN the body is "<<not json>>" with content-type: application/json
  • THEN the call rejects with code === "CURRENCY_PROVIDER.PARSE_ERROR".

Scenario: non-object payload (e.g. JSON null) raises PARSE_ERROR

  • WHEN the body is "null" parsed as JSON
  • THEN the call rejects with code === "CURRENCY_PROVIDER.PARSE_ERROR".

Scenario: upstream error payload raises UPSTREAM_ERROR

  • WHEN the payload is { Response: "Error", Message: "tsym XYZ is not available" }
  • THEN the call rejects with code === "CURRENCY_PROVIDER.UPSTREAM_ERROR" and upstreamMessage contains "tsym XYZ".

Scenario: upstream error payload without Message uses default text

  • WHEN the payload is { Response: "Error" }
  • THEN the call rejects with code === "CURRENCY_PROVIDER.UPSTREAM_ERROR" and upstreamMessage === "unknown upstream error".

Scenario: empty result map raises UNKNOWN_CURRENCY

  • WHEN fetchRates({ base: "USD", targets: ["BTC"] }) is called and the payload is {}
  • THEN the call rejects with code === "CURRENCY_PROVIDER.UNKNOWN_CURRENCY".

Scenario: every quote non-finite or non-positive raises UNKNOWN_CURRENCY

  • WHEN the payload contains only 0, NaN, and negative quotes
  • THEN the call rejects with code === "CURRENCY_PROVIDER.UNKNOWN_CURRENCY".

Verified by: src/cryptocompare/cryptocompare.test.ts::"raises HTTP_ERROR on non-2xx response" AND src/cryptocompare/cryptocompare.test.ts::"raises PARSE_ERROR when the response body is not valid JSON" AND src/cryptocompare/cryptocompare.test.ts::"raises PARSE_ERROR when the parsed payload is not an object" AND src/cryptocompare/cryptocompare.test.ts::"raises UPSTREAM_ERROR when payload carries Response='Error' with Message" AND src/cryptocompare/cryptocompare.test.ts::"raises UPSTREAM_ERROR with default message when Message field is absent" AND src/cryptocompare/cryptocompare.test.ts::"raises UNKNOWN_CURRENCY when no symbols resolve to a quote" AND src/cryptocompare/cryptocompare.test.ts::"raises UNKNOWN_CURRENCY when every quote is non-finite or non-positive"


Requirement: Caller AbortSignal forwarded into shared httpRequest

The package SHALL forward RateFetchOptions.signal (when provided) through the shared httpRequest helper so caller-initiated aborts propagate to the underlying FetchLike call.

Scenario: caller signal reaches the injected fetch

  • WHEN fetchRates({ signal: controller.signal, ... }) is called with a recording FetchLike
  • THEN the recorded init.signal is exactly controller.signal.

Verified by: src/cryptocompare/cryptocompare.test.ts::"forwards a caller AbortSignal through httpRequest"