From 7c2e506a2e9bc91635b732400e686bc4526d4687 Mon Sep 17 00:00:00 2001 From: Schalli Date: Wed, 1 Jul 2026 23:39:39 +0200 Subject: [PATCH] =?UTF-8?q?test(09-03):=20RED=20=E2=80=94=20failing=20pars?= =?UTF-8?q?eCert=20spec=20(PEM/DER/PFX/wrong-password/malformed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added parseCert describe block with 5 failing tests - Covers PEM input, DER input, PFX+correct-password, PFX+wrong-password (BadRequestException), malformed input (BadRequestException) - Existing 11 helper tests still pass - Fixtures built via node-forge (RSA-1024, DER from asn1.toDer, PFX via toPkcs12Asn1) --- .../cert-manager/cert-manager.service.spec.ts | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/apps/api/src/cert-manager/cert-manager.service.spec.ts b/apps/api/src/cert-manager/cert-manager.service.spec.ts index eef1a67..5ea98fa 100644 --- a/apps/api/src/cert-manager/cert-manager.service.spec.ts +++ b/apps/api/src/cert-manager/cert-manager.service.spec.ts @@ -1,3 +1,4 @@ +import { BadRequestException } from '@nestjs/common'; import * as forge from 'node-forge'; import { beforeAll, describe, expect, it, vi } from 'vitest'; import { seedCertManagerModule } from './cert-manager.seed'; @@ -128,3 +129,94 @@ describe('CertManagerService helpers', () => { }); }); }); + +// --------------------------------------------------------------------------- +// parseCert — RED tests (CERT-01, CERT-05 read half) +// --------------------------------------------------------------------------- + +describe('parseCert', () => { + let service: CertManagerService; + let certPem: string; + let certDerBuffer: Buffer; + let pfxBuffer: Buffer; + const CN = 'parsecert.example.com'; + + beforeAll(() => { + service = new CertManagerService(); + + // Create a cert+key pair for all fixtures (RSA-1024 for speed) + const keys = forge.pki.rsa.generateKeyPair(1024); + const cert = forge.pki.createCertificate(); + cert.publicKey = keys.publicKey; + cert.serialNumber = '01'; + cert.validity.notBefore = new Date(); + cert.validity.notAfter = new Date(); + cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); + + const attrs = [{ name: 'commonName', value: CN }]; + cert.setSubject(attrs); + cert.setIssuer(attrs); + cert.sign(keys.privateKey, forge.md.sha256.create()); + + // PEM fixture + certPem = forge.pki.certificateToPem(cert); + + // DER fixture — binary encoding required (Pitfall 1) + const derBytes = forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes(); + certDerBuffer = Buffer.from(derBytes, 'binary'); + + // PFX fixture with password 'secret' + const p12Asn1 = forge.pkcs12.toPkcs12Asn1( + keys.privateKey, + [cert], + 'secret', + { algorithm: '3des' }, + ); + const p12DerBytes = forge.asn1.toDer(p12Asn1).getBytes(); + pfxBuffer = Buffer.from(p12DerBytes, 'binary'); + }, 15000); // 15s timeout — RSA keygen can be slow in pure JS + + it('returns CertDetails with subject.cn, fingerprint.sha256, keyType RSA, keyBits, isExpired false for PEM input', async () => { + // RED: parseCert currently throws NotImplementedException — this test will FAIL + const result = await (service.parseCert({ pemText: certPem }) as Promise); + expect(result.subject.cn).toBe(CN); + expect(result.fingerprint.sha256).toMatch(/^[0-9A-F]{2}(:[0-9A-F]{2})+$/); + expect(result.keyType).toBe('RSA'); + expect(result.keyBits).toBe(1024); + expect(result.validity.isExpired).toBe(false); + }); + + it('returns CertDetails with matching subject.cn for DER input', async () => { + // RED: parseCert throws NotImplementedException — this test will FAIL + const result = await (service.parseCert({ + file: { originalname: 'c.der', buffer: certDerBuffer }, + }) as Promise); + expect(result.subject.cn).toBe(CN); + }); + + it('returns CertDetails for PFX with correct password', async () => { + // RED: parseCert throws NotImplementedException — this test will FAIL + const result = await (service.parseCert({ + file: { originalname: 'c.pfx', buffer: pfxBuffer }, + password: 'secret', + }) as Promise); + expect(result.subject.cn).toBe(CN); + }); + + it('throws BadRequestException for PFX with wrong password', async () => { + // RED: currently throws NotImplementedException (not BadRequestException) — FAIL + await expect( + service.parseCert({ + file: { originalname: 'c.pfx', buffer: pfxBuffer }, + password: 'wrong', + }), + ).rejects.toThrow(BadRequestException); + }); + + it('throws BadRequestException for malformed PEM input', async () => { + // RED: currently throws NotImplementedException (not BadRequestException) — FAIL + await expect( + service.parseCert({ pemText: 'not a cert' }), + ).rejects.toThrow(BadRequestException); + }); +});