This document explains the comprehensive E2E testing strategy used for MynaCard integration, covering real hardware interaction patterns and test organization.
The E2E tests are organized in a hierarchical pattern that mirrors real-world usage:
examples/mynacard-e2e/
├── mynacard.e2e.test.ts # Core platform functionality
├── jpki_ap.test.ts # JPKI application public certificates
├── kenhojo_ap.test.ts # Health insurance application
├── kenkaku_ap.test.ts # Residence certificate application
└── randr.e2e.test.ts # Research & development claims verification
let platform: SmartCardPlatform;
let deviceInfos: DeviceInfo[];
beforeAll(async () => {
platformManager = PcscPlatformManager.getInstance();
platform = platformManager.getPlatform();
await platform.init();
deviceInfos = await platform.getDeviceInfo();
}, 30000); // 30 second timeout for hardware initializationEach test suite maintains shared state for:
- Platform instance: Single PC/SC context across tests
- Device list: Available smart card readers
- Timeout handling: Hardware operations can be slow
it("should have at least one reader", () => {
expect(deviceInfos.length, "No readers found").toBeGreaterThan(0);
});
// Skip tests if no hardware available
if (deviceInfos.length === 0) return;Purpose: Gracefully handle environments without smart card hardware.
it("should read certificate file", async () => {
if (deviceInfos.length === 0) return;
let device: SmartCardDevice | null = null;
let card: SmartCard | null = null;
try {
device = await platform.acquireDevice(deviceInfos[0].id);
card = await device.startSession();
// Test operations here...
} finally {
// Guaranteed cleanup
if (card) await card.release();
if (device) await device.release();
}
});Key principles:
- RAII pattern: Acquire resources in try block
- Guaranteed cleanup: Always release in finally block
- Graceful degradation: Skip tests if hardware unavailable
Tests public certificate files that don't require PIN:
const PUBLIC_CERT_FILES = [
{ name: "認証用CA証明書", fid: JPKI_AP_EF.AUTH_CERT_CA },
{ name: "署名用CA証明書", fid: JPKI_AP_EF.SIGN_CERT_CA },
{ name: "認証用証明書", fid: JPKI_AP_EF.AUTH_CERT },
{ name: "署名用証明書", fid: JPKI_AP_EF.SIGN_CERT }
];
for (const { name, fid } of PUBLIC_CERT_FILES) {
it(`${name} READ BINARY`, async () => {
// Test each certificate file individually
await card.transmit(selectDf(JPKI_AP));
await card.transmit(selectEf(fid));
const readResp = await card.transmit(readBinary(0, 10));
expect(readResp.sw1).toBeTypeOf("number");
expect(readResp.sw2).toBeTypeOf("number");
});
}Testing strategy:
- Parameterized tests: Loop through all certificate types
- Non-invasive: Only read public data (no PIN required)
- Minimal reads: Read small amounts to verify accessibility
Similar pattern but focused on public areas:
const PUBLIC_FILES = [
{ name: "券面事項入力補助AP", fid: [0x00, 0x11] },
{ name: "券面事項入力補助用PIN", fid: [0x00, 0x12] }
];
describe("KENHOJO_AP public area E2E tests", () => {
for (const { name, fid } of PUBLIC_FILES) {
it(`${name} READ BINARY`, async () => {
// Test file accessibility without PIN
});
}
});This test suite verifies specific technical claims about MynaCard capabilities:
describe("RSA-PSS signature verification (80 2A 05 00)", () => {
it("should support PSS algorithm 05 for Kenhojo PIN-less signature EF", async () => {
// Select Kenhojo application
const selectKenhojoApResponse = await card.transmit(selectKenhojoApCommand);
expect(selectKenhojoApResponse.sw).toBe(0x9000);
// Select PIN-less signature EF
await card.transmit(selectEf(KENHOJO_AP_EF.SIGN_PINLESS));
// Test RSA-PSS signature (algorithm 05)
const command802A0500 = new CommandApdu(0x80, 0x2A, 0x05, 0x00, testData);
const response = await card.transmit(command802A0500);
// Verify PSS signature was created
expect(response.sw).toBe(0x9000);
expect(response.data.length).toBeGreaterThan(0);
});
});describe("PIN-free internal authentication verification (80 2A 00 00)", () => {
it("should perform signature without PIN authentication", async () => {
// Test that certain operations don't require PIN verification
const response = await card.transmit(command802A0000);
expect(response.sw).toBe(0x9000);
});
});describe("Authentication command verification (80 A2)", () => {
it("should fail signature verification with invalid data (67 00)", async () => {
// Send meaningless data that should fail CA signature verification
const command80A200C1 = new CommandApdu(0x80, 0xA2, 0x00, 0xC1, invalidData);
const response = await card.transmit(command80A200C1);
// Expect specific error code for verification failure
expect(response.sw).toBe(0x6700);
});
});// Use actual MynaCard constants and file structures
const testData = new Uint8Array([
0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20,
// SHA-256 hash of test data...
]);// Generate test data based on card responses
const atr = await card.getAtr();
const testHash = await crypto.subtle.digest('SHA-256', atr);// Test edge cases and error conditions
const oversizedApdu = new Uint8Array(65540); // Exceeds APDU limits
expect(() => card.transmit(oversizedApdu)).toThrow();it("should respect timeout limits", async () => {
const start = Date.now();
try {
await device.waitForCardPresence(1000); // 1 second timeout
} catch (error) {
const elapsed = Date.now() - start;
expect(elapsed).toBeLessThan(1500); // Should timeout within reasonable bounds
expect(error.code).toBe('TIMEOUT');
}
});it("should handle concurrent APDU transmission safely", async () => {
const promises = Array(5).fill(0).map(() =>
card.transmit(new CommandApdu(0x00, 0xB0, 0x00, 0x00, null, 1))
);
// All operations should complete without interference
const responses = await Promise.all(promises);
responses.forEach(resp => expect(resp).toBeDefined());
});// Test Windows-specific reader name encoding
if (process.platform === 'win32') {
it("should handle UTF-16LE reader names", async () => {
const devices = await platform.getDeviceInfo();
devices.forEach(device => {
expect(device.name).not.toContain('\0'); // Null terminator should be stripped
});
});
}// Test macOS PCSC framework behavior
if (process.platform === 'darwin') {
it("should work with PCSC.framework", async () => {
// macOS-specific tests
});
}// Test Linux PC/SC lite behavior
if (process.platform === 'linux') {
it("should work with pcscd service", async () => {
// Linux-specific tests
});
}// Bad: Generic names
it("should work", () => {});
// Good: Specific, meaningful names
it("should read JPKI authentication certificate without PIN", async () => {});
it("should fail RSA-PSS signature with invalid algorithm parameter", async () => {});// Each test should be self-contained
describe("JPKI certificate reading", () => {
beforeEach(async () => {
// Reset to known state before each test
if (card) {
await card.reset();
}
});
it("should read auth certificate", () => {
// Test doesn't depend on other tests
});
});it("should have adequate response data", () => {
expect(response.data.length,
`Certificate data too small: ${response.data.length} bytes`
).toBeGreaterThan(100);
});This comprehensive E2E testing strategy ensures reliable integration with real MynaCard hardware while providing clear documentation of expected card behavior and API usage patterns.