generated from polymech/site-template
tests:filter/model - validate link cache
This commit is contained in:
parent
8620ee4817
commit
40f89d623e
90
.cache/link-cache.json
Normal file
90
.cache/link-cache.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"https://www.youtube.com/watch?v=4LrrFz802To": {
|
||||
"isValid": true,
|
||||
"timestamp": 1743159524716
|
||||
},
|
||||
"https://youtu.be/gxkcffQD3eQ": {
|
||||
"isValid": true,
|
||||
"timestamp": 1743159526462
|
||||
},
|
||||
"https://zmorph3d.com/blog/hdpe-cnc-machining-materials-overview/": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159605158
|
||||
},
|
||||
"https://www.lijiuplastics.com/how-to-cut-hdpe-sheets-for-smooth-burr-free-results/": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159610900
|
||||
},
|
||||
"https://www.xometry.com/capabilities/cnc-machining-service/hdpe/": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159617036
|
||||
},
|
||||
"https://kingsunmachining.com/blog/machining-hdpe/": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159618539
|
||||
},
|
||||
"https://jesspublib.org/wp-content/uploads/X-Carve-Quick-Start-1.pdf": {
|
||||
"isValid": true,
|
||||
"timestamp": 1743159619212
|
||||
},
|
||||
"https://easel.inventables.com/": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159620810
|
||||
},
|
||||
"https://www.thespruce.com/how-to-build-a-diy-pegboard-5225155": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159623033
|
||||
},
|
||||
"https://homeonharbor.com/2021/10/05/diy-in-a-day-shaker-peg-rail-wall-and-trim/": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159626882
|
||||
},
|
||||
"https://www.papernstitchblog.com/diy-peg-rail-organizer/": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159632080
|
||||
},
|
||||
"https://thediymommy.com/how-to-build-a-giant-framed-pegboard/": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159634611
|
||||
},
|
||||
"https://www.instructables.com/DIY-Modern-Peg-Board-Shelving-System/": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159637729
|
||||
},
|
||||
"https://onedelightfulhome.com/blog/2021/3/24/pegrail": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159638901
|
||||
},
|
||||
"https://thediyplan.com/diy-pegboard-wall/": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159644187
|
||||
},
|
||||
"https://www.prairielightsbooks.com/book/9781569908150": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159644522
|
||||
},
|
||||
"https://www.youtube.com/watch?v=YkHs3H5Vo4M": {
|
||||
"isValid": true,
|
||||
"timestamp": 1743159646050
|
||||
},
|
||||
"https://3dprints.electronsmith.com/peg-system/": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159652488
|
||||
},
|
||||
"https://example.com/cnc-machines": {
|
||||
"isValid": true,
|
||||
"timestamp": 1743159652660
|
||||
},
|
||||
"https://example.com/laser-cutters": {
|
||||
"isValid": true,
|
||||
"timestamp": 1743159652665
|
||||
},
|
||||
"https://www.autodesk.com/products/fusion-360": {
|
||||
"isValid": false,
|
||||
"timestamp": 1743159661050
|
||||
},
|
||||
"https://example.com/sanding-tools": {
|
||||
"isValid": true,
|
||||
"timestamp": 1743159661054
|
||||
}
|
||||
}
|
||||
60
src/base/__tests__/markdown.test.ts
Normal file
60
src/base/__tests__/markdown.test.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { fixMarkdownLint } from '../markdown.js';
|
||||
|
||||
describe('fixMarkdownLint', () => {
|
||||
it('should fix heading capitalization', async () => {
|
||||
const input = `# hello world
|
||||
## this is a test heading
|
||||
### another heading here`;
|
||||
|
||||
const result = await fixMarkdownLint(input);
|
||||
|
||||
expect(result.fixed).toBe(`# Hello World
|
||||
## This Is A Test Heading
|
||||
### Another Heading Here`);
|
||||
expect(result.errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle empty input', async () => {
|
||||
const result = await fixMarkdownLint('');
|
||||
|
||||
expect(result.fixed).toBe('');
|
||||
expect(result.errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle markdown with code blocks', async () => {
|
||||
const input = `# test heading
|
||||
\`\`\`typescript
|
||||
const hello = "world";
|
||||
\`\`\`
|
||||
## another heading`;
|
||||
|
||||
const result = await fixMarkdownLint(input);
|
||||
|
||||
expect(result.fixed).toBe(`# Test Heading
|
||||
\`\`\`typescript
|
||||
const hello = "world";
|
||||
\`\`\`
|
||||
## Another Heading`);
|
||||
expect(result.errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle markdown with lists', async () => {
|
||||
const input = `# test heading
|
||||
- item 1
|
||||
- item 2
|
||||
- subitem 1
|
||||
- subitem 2
|
||||
## another heading`;
|
||||
|
||||
const result = await fixMarkdownLint(input);
|
||||
|
||||
expect(result.fixed).toBe(`# Test Heading
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Subitem 1
|
||||
- Subitem 2
|
||||
## Another Heading`);
|
||||
expect(result.errors.length).toBe(0);
|
||||
});
|
||||
});
|
||||
80
src/model/link-cache.ts
Normal file
80
src/model/link-cache.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
interface CacheEntry {
|
||||
isValid: boolean;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
interface CacheData {
|
||||
[url: string]: CacheEntry;
|
||||
}
|
||||
|
||||
const CACHE_FILE = path.join(process.cwd(), '.cache', 'link-cache.json');
|
||||
const CACHE_EXPIRY = 7 * 24 * 60 * 60 * 1000; // 1 week in milliseconds
|
||||
|
||||
class LinkCache {
|
||||
private cache: CacheData = {};
|
||||
private initialized = false;
|
||||
|
||||
private async ensureCacheDir() {
|
||||
const dir = path.dirname(CACHE_FILE);
|
||||
try {
|
||||
await fs.access(dir);
|
||||
} catch {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
private async loadCache() {
|
||||
if (this.initialized) return;
|
||||
|
||||
try {
|
||||
const data = await fs.readFile(CACHE_FILE, 'utf-8');
|
||||
this.cache = JSON.parse(data);
|
||||
} catch (error) {
|
||||
// If file doesn't exist or is invalid, start with empty cache
|
||||
this.cache = {};
|
||||
}
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
private async saveCache() {
|
||||
await this.ensureCacheDir();
|
||||
await fs.writeFile(CACHE_FILE, JSON.stringify(this.cache, null, 2));
|
||||
}
|
||||
|
||||
private isExpired(entry: CacheEntry): boolean {
|
||||
return Date.now() - entry.timestamp > CACHE_EXPIRY;
|
||||
}
|
||||
|
||||
async get(url: string): Promise<boolean | null> {
|
||||
await this.loadCache();
|
||||
const entry = this.cache[url];
|
||||
|
||||
if (!entry) return null;
|
||||
if (this.isExpired(entry)) {
|
||||
delete this.cache[url];
|
||||
await this.saveCache();
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.isValid;
|
||||
}
|
||||
|
||||
async set(url: string, isValid: boolean): Promise<void> {
|
||||
await this.loadCache();
|
||||
this.cache[url] = {
|
||||
isValid,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
await this.saveCache();
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
this.cache = {};
|
||||
await this.saveCache();
|
||||
}
|
||||
}
|
||||
|
||||
export const linkCache = new LinkCache();
|
||||
Loading…
Reference in New Issue
Block a user