Files
edr-platform/apps/edr-freight-api/src/modules/eims/eims-test-fixtures.ts
Hagernesh 13f7bea590 feat(eims): accept private key/cert as raw PEM env vars
EIMS_PRIVATE_KEY / EIMS_CERTIFICATE — the PEM text pasted directly,
no encode/decode step at all. Precedence: raw PEM > base64 > path.

Motivated by the base64 path hitting a DECODER::unsupported error in
a live deployment with no way to tell whether the cause was transport
truncation, double-encoding, or an actually-bad file. Two fixes for
that class of problem together:
  - the raw-PEM var removes the encode/decode step entirely, so
    there's nothing left to corrupt in transit
  - a literal \\n (two chars) is unescaped to a real newline, for
    env stores that can't hold a literal line break
  - getPrivateKey() now checks the decoded bytes look like a PEM
    header before handing them to OpenSSL, so a still-bad value fails
    with byte count + safe preview instead of an opaque decoder error
2026-08-17 11:03:22 +00:00

93 lines
3.0 KiB
TypeScript

import { EimsConfig, EimsInvoiceConfig } from "../../config/eims.config";
/**
* Fixtures shared by the EIMS specs.
*
* Deliberately not a `.spec.ts`: importing fixtures from a spec file makes jest execute that
* file's `describe` blocks inside every importing suite, so the same tests run — and report —
* twice.
*/
export const EIMS_SYSTEM_NUMBER = "B0360154BA";
export const EIMS_SYSTEM_TYPE = "SYS";
export const eimsInvoiceConfig = (over: Partial<EimsInvoiceConfig> = {}): EimsInvoiceConfig => ({
sellerLegalName: "Ethio-Djibouti Railway S.C.",
sellerVatNumber: "0000000000",
sellerPhone: "0911223344",
sellerEmail: "finance@example.et",
sellerRegion: "13",
sellerWereda: "574",
sellerCity: null,
sellerSubCity: null,
sellerHouseNumber: null,
sellerLocality: null,
taxCode: "VAT15",
taxRatePercent: 15,
exciseTaxValue: 0,
incomeWithholdValue: 0,
transactionWithholdValue: 0,
transactionType: "B2B",
natureOfSupplies: "Service",
paymentMode: "CASH",
paymentTerm: "IMMIDIATE",
unitDefault: "PCS",
buyerCountryCode: null,
buyerCountryCodes: { Ethiopia: "231" }, // test-only, not a confirmed real MoR code
buyerRegionCodes: { "Addis Ababa": "13" },
buyerWeredaCodes: { Yeka: "99" }, // test-only, not a real MoR code
buyerCityCodes: { Kirkos: "101" }, // test-only, not a confirmed real MoR code
taxCodeByChargeType: {},
taxRateByChargeType: {},
exciseByChargeType: {},
discountByChargeType: {},
cashierName: null,
salesPersonName: null,
buyerIdType: null,
buyerIdNumber: null,
...over,
});
export const eimsConfig = (over: Partial<EimsConfig> = {}): EimsConfig => ({
enabled: true,
baseUrl: "https://core.mor.gov.et",
clientId: "cid",
clientSecret: "super-secret-value",
apiKey: "super-secret-apikey",
tin: "0000034558",
systemNumber: EIMS_SYSTEM_NUMBER,
systemType: EIMS_SYSTEM_TYPE,
privateKeyPath: "/dev/null",
certificatePath: "/dev/null",
privateKeyBase64: "",
certificateBase64: "",
privateKeyPem: "",
certificatePem: "",
httpTimeoutMs: 30_000,
tokenSkewMs: 45_000,
autoSubmit: false,
autoSubmitCron: "0 */5 * * * *",
autoSubmitMaxAgeDays: 3,
invoice: eimsInvoiceConfig(),
...over,
});
/**
* A structurally real access token. MoR stamps the source-system identity into the JWT payload and
* `EimsAuthService` reads it from there; only the payload segment is meaningful, since the token is
* never verified locally — it is MoR's, signed with MoR's key.
*
* Pass a claim as `undefined` to omit it (spreading beats `delete`, which the defaults would undo).
*/
export const eimsToken = (claims: Record<string, unknown> = {}): string => {
const payload = { systemNumber: EIMS_SYSTEM_NUMBER, systemType: EIMS_SYSTEM_TYPE, ...claims };
for (const [key, value] of Object.entries(payload)) {
if (value === undefined) delete (payload as Record<string, unknown>)[key];
}
return [
"eyJhbGciOiJSUzI1NiJ9",
Buffer.from(JSON.stringify(payload)).toString("base64url"),
"signature",
].join(".");
};