Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions cornucopia.owasp.org/src/lib/services/deckService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,60 @@ suits:

consoleLogSpy.mockRestore();
});

it('should warn and return empty map if card file is missing', () => {
vi.mocked(FileSystemHelper.hasFile).mockReturnValue(false);

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

const deckService = new DeckService();
const result = deckService.getCardDataForEditionVersionLang('webapp', '2.2', 'en');

expect(result.size).toBe(0);
expect(consoleWarnSpy).toHaveBeenCalled();

consoleWarnSpy.mockRestore();
});

it('should handle generic markdown read error gracefully', () => {
vi.mocked(FileSystemHelper.hasFile).mockReturnValue(true);
vi.mocked(FileSystemHelper.hasDir).mockReturnValue(true);

const mockYamlContent = `
suits:
- id: suit1
name: Test Suit
cards:
- id: CARD-1
value: A
desc: Card 1
`;

// First call → YAML
// Second call → throw error (simulate markdown failure)
vi.mocked(fs.readFileSync)
.mockReturnValueOnce(mockYamlContent)
.mockImplementationOnce(() => {
throw new Error('Markdown read failed');
});

const mockMapping = {
suits: {
'0': { name: 'Test Suit' }
}
};
vi.mocked(MappingService.prototype.getCardMapping).mockReturnValue(mockMapping as any);

const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

const deckService = new DeckService();
const result = deckService.getCardDataForEditionVersionLang('webapp', '2.2', 'en');

expect(result.size).toBe(0);
expect(consoleErrorSpy).toHaveBeenCalled();

consoleErrorSpy.mockRestore();
});
}, 10000);

describe('clear', () => {
Expand Down
11 changes: 8 additions & 3 deletions cornucopia.owasp.org/src/lib/services/deckService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export class DeckService {
let cardFile = `${__dirname}${DeckService.path}${edition}-cards-${version}-${lang}.yaml`;

if (!FileSystemHelper.hasFile(cardFile)) {
console.warn(`Card file not found: ${cardFile}`);
return cards;
}

Expand Down Expand Up @@ -126,15 +127,19 @@ export class DeckService {
try {
file = fs.readFileSync(path, 'utf8');
} catch (e) {
console.error(`Error reading file at path: ${path}`, e);
console.error(
`Error reading markdown file for card ${cardObject?.id || "unknown"} at ${path}`,e
);
continue;
}
let parsed = fm(file);
cardObject.concept = parsed.body;
const explanationPath = `./${base}${cardFolderPath}/explanation.md`;
try {
cardObject.summary = fm(fs.readFileSync(`./${base}${cardFolderPath}/explanation.md`, 'utf8')).body;
cardObject.summary = fm(fs.readFileSync(explanationPath, 'utf8')).body;
} catch (e) {
console.error(`Error reading file at path: ./${base}${cardFolderPath}/explanation.md`, e);
console.error(
`Missing explanation.md for card ${cardObject?.id || "unknown"} at ${explanationPath}`,e);
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,36 @@ describe('GET /api/mapping/[edition]/[version]', () => {
expect(body.suits).toBeUndefined();
});

it('skips invalid suits and duplicate or invalid cards while transforming suits', async () => {
vi.spyOn(DeckService, 'hasEdition').mockReturnValue(true);
vi.spyOn(DeckService, 'hasVersion').mockReturnValue(true);
vi.spyOn(MappingService.prototype, 'getCardMapping').mockReturnValue({
suits: [
null,
{},
{
cards: [
null,
{ id: '' },
{ id: 'VE2', value: '2' },
{ id: 'VE2', value: 'duplicate' }
]
}
]
} as any);

const response = await GET({
params: { edition: 'webapp', version: '3.0' }
} as any);

expect(response.status).toBe(200);
const body = await response.json();
expect(body.meta).toBeUndefined();
expect(body.cards).toEqual({
VE2: { id: 'VE2', value: '2' }
});
});

it('throws 404 when edition is invalid', () => {
vi.spyOn(DeckService, 'hasEdition').mockReturnValue(false);
vi.spyOn(DeckService, 'getLatestEditions').mockReturnValue(['webapp', 'mobileapp']);
Expand Down
4 changes: 2 additions & 2 deletions cornucopia.owasp.org/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ let vitePluginRestartOptions = {restart: ['./data/**']}
// This copies the content from the filesystem data folder to the static file location under '/data/' available at runtime.
// Also copies to server output for prerendering
let viteStaticCopyTargets = [
{src: './data/**/*', dest: './data/'},
{src: './data/**/*', dest: '../server/data/'}
{src: './data/**/*', dest: './'},
{src: './data/**/*', dest: '../server/'}
]
let viteStaticCopyOptions = { targets: viteStaticCopyTargets}

Expand Down
Loading