Skip to content

Commit

Permalink
chore: add tests
Browse files Browse the repository at this point in the history
chore: wip
  • Loading branch information
chrisbbreuer committed Feb 20, 2025
1 parent 32101cc commit 982596c
Showing 1 changed file with 246 additions and 11 deletions.
257 changes: 246 additions & 11 deletions test/tlsx.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
import type { CertificateOptions } from '../src/types'
import { describe, expect, it } from 'bun:test'
import { createRootCA, generateCertificate, isCertExpired, isCertValidForDomain, parseCertDetails } from '../src'
import type { CertificateOptions, CAOptions, Certificate } from '../src/types'
import { describe, expect, it, beforeEach, afterEach } from 'bun:test'
import { createRootCA, generateCertificate, isCertExpired, isCertValidForDomain, parseCertDetails, findFoldersWithFile, listCertsInDirectory, getCertificateFromCertPemOrPath } from '../src'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { writeFileSync, mkdirSync, rmSync } from 'node:fs'
import * as forge from 'node-forge'

describe('@stacksjs/tlsx', () => {
it('should create a Root CA certificate', async () => {
const rootCA = await createRootCA()
let rootCA: Certificate

beforeEach(async () => {
rootCA = await createRootCA()
})

it('should create a Root CA certificate', () => {
expect(rootCA).toHaveProperty('certificate')
expect(rootCA).toHaveProperty('privateKey')
expect(rootCA).toHaveProperty('notBefore')
expect(rootCA).toHaveProperty('notAfter')
})

it('should generate a host certificate', async () => {
const rootCA = await createRootCA()
const options: CertificateOptions = {
hostCertCN: 'localhost',
domain: 'localhost',
rootCA: {
certificate: rootCA.certificate,
privateKey: rootCA.privateKey,
},
certificate: '',
privateKey: '',
}
const hostCert = await generateCertificate(options)
expect(hostCert).toHaveProperty('certificate')
Expand All @@ -29,22 +39,22 @@ describe('@stacksjs/tlsx', () => {
})

it('should validate a certificate for a domain', async () => {
const rootCA = await createRootCA()
const options: CertificateOptions = {
hostCertCN: 'localhost',
domain: 'localhost',
rootCA: {
certificate: rootCA.certificate,
privateKey: rootCA.privateKey,
},
certificate: '',
privateKey: '',
}
const hostCert = await generateCertificate(options)
const isValid = isCertValidForDomain(hostCert.certificate, 'localhost')
expect(isValid).toBe(true)
})

it('should parse certificate details', async () => {
const rootCA = await createRootCA()
it('should parse certificate details', () => {
const certDetails = parseCertDetails(rootCA.certificate)
expect(certDetails).toHaveProperty('subject')
expect(certDetails).toHaveProperty('issuer')
Expand All @@ -53,9 +63,234 @@ describe('@stacksjs/tlsx', () => {
expect(certDetails).toHaveProperty('serialNumber')
})

it('should check if a certificate is expired', async () => {
const rootCA = await createRootCA()
it('should check if a certificate is expired', () => {
const isExpired = isCertExpired(rootCA.certificate)
expect(isExpired).toBe(false)
})

it('should generate a certificate with multiple domains', async () => {
const options: CertificateOptions = {
hostCertCN: 'example.com',
domain: 'example.com',
domains: ['sub1.example.com', 'sub2.example.com'],
rootCA: {
certificate: rootCA.certificate,
privateKey: rootCA.privateKey,
},
certificate: '',
privateKey: '',
}
const hostCert = await generateCertificate(options)
expect(hostCert).toHaveProperty('certificate')

// Verify each domain is valid
expect(isCertValidForDomain(hostCert.certificate, 'example.com')).toBe(true)
expect(isCertValidForDomain(hostCert.certificate, 'sub1.example.com')).toBe(true)
expect(isCertValidForDomain(hostCert.certificate, 'sub2.example.com')).toBe(true)
expect(isCertValidForDomain(hostCert.certificate, 'invalid.example.com')).toBe(false)
})

it('should generate a certificate with IP addresses and URIs', async () => {
const options: CertificateOptions = {
hostCertCN: 'localhost',
domain: 'localhost',
altNameIPs: ['127.0.0.1', '::1'],
altNameURIs: ['https://localhost/app'],
rootCA: {
certificate: rootCA.certificate,
privateKey: rootCA.privateKey,
},
certificate: '',
privateKey: '',
}
const hostCert = await generateCertificate(options)
const cert = getCertificateFromCertPemOrPath(hostCert.certificate)
const altNames = cert.getExtension('subjectAltName')

const altNamesList = (altNames as forge.pki.SubjectAltNameExtension).altNames
expect(altNamesList.some(name => name.type === 7 && name.value === '127.0.0.1')).toBe(true)
expect(altNamesList.some(name => name.type === 7 && name.value === '::1')).toBe(true)
expect(altNamesList.some(name => name.type === 6 && name.value === 'https://localhost/app')).toBe(true)
})

it('should generate a certificate with custom validity period', async () => {
const validityYears = 1
const customCA = await createRootCA({
validityYears,
organization: 'Test CA',
commonName: 'Test Root CA',
} as CAOptions)
const now = new Date()

// The certificate should be valid from now
expect(customCA.notBefore.getTime()).toBeLessThanOrEqual(now.getTime())

// The certificate should be valid for roughly validityYears (with some buffer for test execution)
const expectedValidityMs = validityYears * 365 * 24 * 60 * 60 * 1000
const actualValidityMs = customCA.notAfter.getTime() - customCA.notBefore.getTime()
const tolerance = 7 * 24 * 60 * 60 * 1000 // 7 days tolerance for leap years

expect(Math.abs(actualValidityMs - expectedValidityMs)).toBeLessThan(tolerance)
})

it('should generate a certificate with custom organization details', async () => {
const customOrg: CAOptions = {
organization: 'Test Corp',
organizationalUnit: 'Test Unit',
countryName: 'US',
stateName: 'California',
localityName: 'San Francisco',
commonName: 'Test Root CA',
}

const customCA = await createRootCA(customOrg)
const details = parseCertDetails(customCA.certificate)
const subject = details.subject

// Check each field in the subject
const orgField = subject.find(field => field.shortName === 'O')
expect(orgField?.value).toBe('Test Corp')

const countryField = subject.find(field => field.shortName === 'C')
expect(countryField?.value).toBe('US')

const stateField = subject.find(field => field.shortName === 'ST')
expect(stateField?.value).toBe('California')

const localityField = subject.find(field => field.shortName === 'L')
expect(localityField?.value).toBe('San Francisco')

const ouField = subject.find(field => field.shortName === 'OU')
expect(ouField?.value).toBe('Test Unit')
})

it('should handle invalid certificate data', () => {
expect(() => isCertValidForDomain('invalid-cert-data', 'example.com')).toThrow()
expect(() => parseCertDetails('invalid-cert-data')).toThrow()
expect(() => isCertExpired('invalid-cert-data')).toThrow()
})

describe('file system operations', () => {
let tempDir: string

beforeEach(() => {
tempDir = join(tmpdir(), `tlsx-test-${Date.now()}`)
mkdirSync(tempDir, { recursive: true })
})

afterEach(() => {
rmSync(tempDir, { recursive: true, force: true })
})

it('should find folders with specific files', () => {
const testDir1 = join(tempDir, 'test1')
const testDir2 = join(tempDir, 'test2')
const testDir3 = join(tempDir, 'test3')

mkdirSync(testDir1, { recursive: true })
mkdirSync(testDir2, { recursive: true })
mkdirSync(testDir3, { recursive: true })

writeFileSync(join(testDir1, 'cert.pem'), 'test')
writeFileSync(join(testDir3, 'cert.pem'), 'test')

const foundDirs = findFoldersWithFile(tempDir, 'cert.pem')
expect(foundDirs).toContain(testDir1)
expect(foundDirs).toContain(testDir3)
expect(foundDirs).not.toContain(testDir2)
})

it('should list certificates in directory', () => {
writeFileSync(join(tempDir, 'cert1.crt'), 'test')
writeFileSync(join(tempDir, 'cert2.crt'), 'test')
writeFileSync(join(tempDir, 'key.pem'), 'test')

const certs = listCertsInDirectory(tempDir)
const certFiles = certs.filter(cert => cert.startsWith(tempDir))
.map(cert => cert.split('/').pop())

expect(certFiles).toContain('cert1.crt')
expect(certFiles).toContain('cert2.crt')
expect(certFiles).not.toContain('key.pem')
})
})

describe('certificate extensions', () => {
it('should generate a certificate with key usage extensions', async () => {
const options: CertificateOptions = {
hostCertCN: 'localhost',
domain: 'localhost',
rootCA: {
certificate: rootCA.certificate,
privateKey: rootCA.privateKey,
},
certificate: '',
privateKey: '',
keyUsage: {
digitalSignature: true,
keyEncipherment: true,
},
extKeyUsage: {
serverAuth: true,
clientAuth: true,
},
}
const hostCert = await generateCertificate(options)
const cert = getCertificateFromCertPemOrPath(hostCert.certificate)

const keyUsage = cert.getExtension('keyUsage') as forge.pki.KeyUsageExtension
expect(keyUsage.digitalSignature).toBe(true)
expect(keyUsage.keyEncipherment).toBe(true)

const extKeyUsage = cert.getExtension('extKeyUsage') as forge.pki.ExtendedKeyUsageExtension
expect(extKeyUsage.serverAuth).toBe(true)
expect(extKeyUsage.clientAuth).toBe(true)
})

it('should generate a certificate with basic constraints', async () => {
const options: CertificateOptions = {
hostCertCN: 'localhost',
domain: 'localhost',
rootCA: {
certificate: rootCA.certificate,
privateKey: rootCA.privateKey,
},
certificate: '',
privateKey: '',
basicConstraints: {
cA: true,
pathLenConstraint: 1,
},
}
const hostCert = await generateCertificate(options)
const cert = getCertificateFromCertPemOrPath(hostCert.certificate)

const basicConstraints = cert.getExtension('basicConstraints') as forge.pki.BasicConstraintsExtension
expect(basicConstraints.cA).toBe(true)
expect(basicConstraints.pathLenConstraint).toBe(1)
})

it('should generate a certificate with custom attributes', async () => {
const options: CertificateOptions = {
hostCertCN: 'localhost',
domain: 'localhost',
rootCA: {
certificate: rootCA.certificate,
privateKey: rootCA.privateKey,
},
certificate: '',
privateKey: '',
certificateAttributes: [
{ shortName: 'OU', value: 'Test Unit' },
{ shortName: 'O', value: 'Test Corp' },
],
}
const hostCert = await generateCertificate(options)
const details = parseCertDetails(hostCert.certificate)

const subject = details.subject
expect(subject.find(field => field.shortName === 'OU')?.value).toBe('Test Unit')
expect(subject.find(field => field.shortName === 'O')?.value).toBe('Test Corp')
})
})
})

0 comments on commit 982596c

Please sign in to comment.