chore: stabilize apps/web (lint, build, typecheck fixes)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Failing after 1m27s
Build & Deploy / 🏗️ Build (push) Failing after 1m31s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s

This commit is contained in:
2026-02-11 11:56:13 +01:00
parent 8ba81809b0
commit ecea90dc91
50 changed files with 5596 additions and 3456 deletions

View File

@@ -2,6 +2,8 @@
* Analytics interfaces - decoupled contracts
*/
/* eslint-disable no-unused-vars */
export interface AnalyticsEvent {
name: string;
props?: Record<string, any>;
@@ -9,12 +11,12 @@ export interface AnalyticsEvent {
export interface AnalyticsAdapter {
track(event: AnalyticsEvent): Promise<void>;
identify?(userId: string, traits?: Record<string, any>): Promise<void>;
page?(path: string, props?: Record<string, any>): Promise<void>;
identify?(_userId: string, _traits?: Record<string, any>): Promise<void>;
page?(_path: string, _props?: Record<string, any>): Promise<void>;
getScriptTag?(): string;
}
export interface AnalyticsConfig {
domain?: string;
scriptUrl?: string;
}
}

View File

@@ -1,96 +1,101 @@
import type { CacheAdapter, CacheConfig } from './interfaces';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { existsSync, mkdirSync } from 'node:fs';
import * as crypto from 'node:crypto';
import type { CacheAdapter, CacheConfig } from "./interfaces";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { existsSync } from "node:fs";
import * as crypto from "node:crypto";
/* eslint-disable no-unused-vars */
export class FileCacheAdapter implements CacheAdapter {
private cacheDir: string;
private prefix: string;
private defaultTTL: number;
private cacheDir: string;
private prefix: string;
private defaultTTL: number;
constructor(config?: CacheConfig & { cacheDir?: string }) {
this.cacheDir = config?.cacheDir || path.resolve(process.cwd(), '.cache');
this.prefix = config?.prefix || '';
this.defaultTTL = config?.defaultTTL || 3600;
constructor(config?: CacheConfig & { cacheDir?: string }) {
this.cacheDir = config?.cacheDir || path.resolve(process.cwd(), ".cache");
this.prefix = config?.prefix || "";
this.defaultTTL = config?.defaultTTL || 3600;
if (!existsSync(this.cacheDir)) {
fs.mkdir(this.cacheDir, { recursive: true }).catch(err => {
console.error(`Failed to create cache directory: ${this.cacheDir}`, err);
});
}
}
private sanitize(key: string): string {
const clean = key.replace(/[^a-z0-9]/gi, '_');
if (clean.length > 64) {
return crypto.createHash('md5').update(key).digest('hex');
}
return clean;
}
private getFilePath(key: string): string {
const safeKey = this.sanitize(`${this.prefix}${key}`).toLowerCase();
return path.join(this.cacheDir, `${safeKey}.json`);
}
async get<T>(key: string): Promise<T | null> {
const filePath = this.getFilePath(key);
try {
if (!existsSync(filePath)) return null;
const content = await fs.readFile(filePath, 'utf8');
const data = JSON.parse(content);
if (data.expiry && Date.now() > data.expiry) {
await this.del(key);
return null;
}
return data.value;
} catch (error) {
return null;
}
}
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
const filePath = this.getFilePath(key);
const effectiveTTL = ttl !== undefined ? ttl : this.defaultTTL;
const data = {
value,
expiry: effectiveTTL > 0 ? Date.now() + effectiveTTL * 1000 : null,
updatedAt: new Date().toISOString()
};
try {
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
} catch (error) {
console.error(`Failed to write cache file: ${filePath}`, error);
}
}
async del(key: string): Promise<void> {
const filePath = this.getFilePath(key);
try {
if (existsSync(filePath)) {
await fs.unlink(filePath);
}
} catch (error) {
// Ignore
}
}
async clear(): Promise<void> {
try {
if (existsSync(this.cacheDir)) {
const files = await fs.readdir(this.cacheDir);
for (const file of files) {
if (file.endsWith('.json')) {
await fs.unlink(path.join(this.cacheDir, file));
}
}
}
} catch (error) {
// Ignore
if (!existsSync(this.cacheDir)) {
fs.mkdir(this.cacheDir, { recursive: true }).catch((err) => {
console.error(
`Failed to create cache directory: ${this.cacheDir}`,
err,
);
});
}
}
private sanitize(key: string): string {
const clean = key.replace(/[^a-z0-9]/gi, "_");
if (clean.length > 64) {
return crypto.createHash("md5").update(key).digest("hex");
}
return clean;
}
private getFilePath(key: string): string {
const safeKey = this.sanitize(`${this.prefix}${key}`).toLowerCase();
return path.join(this.cacheDir, `${safeKey}.json`);
}
async get<T>(key: string): Promise<T | null> {
const filePath = this.getFilePath(key);
try {
if (!existsSync(filePath)) return null;
const content = await fs.readFile(filePath, "utf8");
const data = JSON.parse(content);
if (data.expiry && Date.now() > data.expiry) {
await this.del(key);
return null;
}
return data.value;
} catch (_error) {
return null;
}
}
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
const filePath = this.getFilePath(key);
const effectiveTTL = ttl !== undefined ? ttl : this.defaultTTL;
const data = {
value,
expiry: effectiveTTL > 0 ? Date.now() + effectiveTTL * 1000 : null,
updatedAt: new Date().toISOString(),
};
try {
await fs.writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
} catch (error) {
console.error(`Failed to write cache file: ${filePath}`, error);
}
}
async del(key: string): Promise<void> {
const filePath = this.getFilePath(key);
try {
if (existsSync(filePath)) {
await fs.unlink(filePath);
}
} catch (_error) {
// Ignore
}
}
async clear(): Promise<void> {
try {
if (existsSync(this.cacheDir)) {
const files = await fs.readdir(this.cacheDir);
for (const file of files) {
if (file.endsWith(".json")) {
await fs.unlink(path.join(this.cacheDir, file));
}
}
}
} catch (_error) {
// Ignore
}
}
}

View File

@@ -3,15 +3,17 @@
* This simulates what happens when a blog post is rendered
*/
import { blogPosts } from '../data/blogPosts';
import { FileExampleManager } from '../data/fileExamples';
import { blogPosts } from "../data/blogPosts";
import { FileExampleManager } from "../data/fileExamples";
/* eslint-disable no-unused-vars */
export async function testBlogPostIntegration() {
console.log('🧪 Testing Blog Post + File Examples Integration...\n');
console.log("🧪 Testing Blog Post + File Examples Integration...\n");
let passed = 0;
let failed = 0;
const test = (name: string, fn: () => void | Promise<void>) => {
try {
const result = fn();
@@ -38,15 +40,15 @@ export async function testBlogPostIntegration() {
};
// Test 1: Blog posts exist
test('Blog posts are loaded', () => {
test("Blog posts are loaded", () => {
if (!blogPosts || blogPosts.length === 0) {
throw new Error('No blog posts found');
throw new Error("No blog posts found");
}
console.log(` Found ${blogPosts.length} posts`);
});
// Test 2: Each post has required fields
test('All posts have required fields', () => {
test("All posts have required fields", () => {
for (const post of blogPosts) {
if (!post.slug || !post.title || !post.tags) {
throw new Error(`Post ${post.slug} missing required fields`);
@@ -55,207 +57,255 @@ export async function testBlogPostIntegration() {
});
// Test 3: Debugging-tips post should have file examples
test('debugging-tips post has file examples', async () => {
const post = blogPosts.find(p => p.slug === 'debugging-tips');
test("debugging-tips post has file examples", async () => {
const post = blogPosts.find((p) => p.slug === "debugging-tips");
if (!post) {
throw new Error('debugging-tips post not found');
throw new Error("debugging-tips post not found");
}
// Check if it would trigger file examples
const showFileExamples = post.tags?.some(tag =>
['architecture', 'design-patterns', 'system-design', 'docker', 'deployment'].includes(tag)
const _showFileExamples = post.tags?.some((tag) =>
[
"architecture",
"design-patterns",
"system-design",
"docker",
"deployment",
].includes(tag),
);
// debugging-tips has tags ['debugging', 'tools'] so showFileExamples would be false
// But it has hardcoded FileExamplesList in the template
// Verify files exist for this post
const groups = await FileExampleManager.getAllGroups();
const filesForPost = groups.flatMap(g => g.files).filter(f => f.postSlug === 'debugging-tips');
const filesForPost = groups
.flatMap((g) => g.files)
.filter((f) => f.postSlug === "debugging-tips");
if (filesForPost.length === 0) {
throw new Error('No files found for debugging-tips');
throw new Error("No files found for debugging-tips");
}
console.log(` Found ${filesForPost.length} files for debugging-tips`);
});
// Test 4: Architecture-patterns post should have file examples
test('architecture-patterns post has file examples', async () => {
const post = blogPosts.find(p => p.slug === 'architecture-patterns');
test("architecture-patterns post has file examples", async () => {
const post = blogPosts.find((p) => p.slug === "architecture-patterns");
if (!post) {
throw new Error('architecture-patterns post not found');
throw new Error("architecture-patterns post not found");
}
// Check if it would trigger file examples
const showFileExamples = post.tags?.some(tag =>
['architecture', 'design-patterns', 'system-design', 'docker', 'deployment'].includes(tag)
const showFileExamples = post.tags?.some((tag) =>
[
"architecture",
"design-patterns",
"system-design",
"docker",
"deployment",
].includes(tag),
);
if (!showFileExamples) {
throw new Error('architecture-patterns should show file examples');
throw new Error("architecture-patterns should show file examples");
}
// Verify files exist for this post
const groups = await FileExampleManager.getAllGroups();
const filesForPost = groups.flatMap(g => g.files).filter(f => f.postSlug === 'architecture-patterns');
const filesForPost = groups
.flatMap((g) => g.files)
.filter((f) => f.postSlug === "architecture-patterns");
if (filesForPost.length === 0) {
throw new Error('No files found for architecture-patterns');
throw new Error("No files found for architecture-patterns");
}
console.log(` Found ${filesForPost.length} files for architecture-patterns`);
console.log(
` Found ${filesForPost.length} files for architecture-patterns`,
);
});
// Test 5: Docker-deployment post should have file examples
test('docker-deployment post has file examples', async () => {
const post = blogPosts.find(p => p.slug === 'docker-deployment');
test("docker-deployment post has file examples", async () => {
const post = blogPosts.find((p) => p.slug === "docker-deployment");
if (!post) {
throw new Error('docker-deployment post not found');
throw new Error("docker-deployment post not found");
}
// Check if it would trigger file examples
const showFileExamples = post.tags?.some(tag =>
['architecture', 'design-patterns', 'system-design', 'docker', 'deployment'].includes(tag)
const showFileExamples = post.tags?.some((tag) =>
[
"architecture",
"design-patterns",
"system-design",
"docker",
"deployment",
].includes(tag),
);
if (!showFileExamples) {
throw new Error('docker-deployment should show file examples');
throw new Error("docker-deployment should show file examples");
}
// Verify files exist for this post
const groups = await FileExampleManager.getAllGroups();
const filesForPost = groups.flatMap(g => g.files).filter(f => f.postSlug === 'docker-deployment');
const filesForPost = groups
.flatMap((g) => g.files)
.filter((f) => f.postSlug === "docker-deployment");
if (filesForPost.length === 0) {
throw new Error('No files found for docker-deployment');
throw new Error("No files found for docker-deployment");
}
console.log(` Found ${filesForPost.length} files for docker-deployment`);
});
// Test 6: First-note post should NOT have file examples
test('first-note post has no file examples', async () => {
const post = blogPosts.find(p => p.slug === 'first-note');
test("first-note post has no file examples", async () => {
const post = blogPosts.find((p) => p.slug === "first-note");
if (!post) {
throw new Error('first-note post not found');
throw new Error("first-note post not found");
}
// Check if it would trigger file examples
const showFileExamples = post.tags?.some(tag =>
['architecture', 'design-patterns', 'system-design', 'docker', 'deployment'].includes(tag)
const showFileExamples = post.tags?.some((tag) =>
[
"architecture",
"design-patterns",
"system-design",
"docker",
"deployment",
].includes(tag),
);
if (showFileExamples) {
throw new Error('first-note should NOT show file examples');
throw new Error("first-note should NOT show file examples");
}
// Verify no files exist for this post
const groups = await FileExampleManager.getAllGroups();
const filesForPost = groups.flatMap(g => g.files).filter(f => f.postSlug === 'first-note');
const filesForPost = groups
.flatMap((g) => g.files)
.filter((f) => f.postSlug === "first-note");
if (filesForPost.length > 0) {
throw new Error('Files found for first-note, but none should exist');
throw new Error("Files found for first-note, but none should exist");
}
console.log(` Correctly has no files`);
});
// Test 7: Simulate FileExamplesList filtering for debugging-tips
test('FileExamplesList filtering works for debugging-tips', async () => {
const postSlug = 'debugging-tips';
const groupId = 'python-data-processing';
test("FileExamplesList filtering works for debugging-tips", async () => {
const postSlug = "debugging-tips";
const groupId = "python-data-processing";
const allGroups = await FileExampleManager.getAllGroups();
const loadedGroups = allGroups
.filter(g => g.groupId === groupId)
.map(g => ({
.filter((g) => g.groupId === groupId)
.map((g) => ({
...g,
files: g.files.filter(f => f.postSlug === postSlug)
files: g.files.filter((f) => f.postSlug === postSlug),
}))
.filter(g => g.files.length > 0);
.filter((g) => g.files.length > 0);
if (loadedGroups.length === 0) {
throw new Error('No groups loaded for debugging-tips with python-data-processing');
throw new Error(
"No groups loaded for debugging-tips with python-data-processing",
);
}
if (loadedGroups[0].files.length === 0) {
throw new Error('No files in the group');
throw new Error("No files in the group");
}
console.log(` Would show ${loadedGroups[0].files.length} files`);
});
// Test 8: Simulate FileExamplesList filtering for architecture-patterns
test('FileExamplesList filtering works for architecture-patterns', async () => {
const postSlug = 'architecture-patterns';
const tags = ['architecture', 'design-patterns', 'system-design'];
test("FileExamplesList filtering works for architecture-patterns", async () => {
const postSlug = "architecture-patterns";
const tags = ["architecture", "design-patterns", "system-design"];
const allGroups = await FileExampleManager.getAllGroups();
const loadedGroups = allGroups
.map(g => ({
.map((g) => ({
...g,
files: g.files.filter(f => {
files: g.files.filter((f) => {
if (f.postSlug !== postSlug) return false;
if (tags && tags.length > 0) {
return f.tags?.some(tag => tags.includes(tag));
return f.tags?.some((tag) => tags.includes(tag));
}
return true;
})
}),
}))
.filter(g => g.files.length > 0);
.filter((g) => g.files.length > 0);
if (loadedGroups.length === 0) {
throw new Error('No groups loaded for architecture-patterns');
throw new Error("No groups loaded for architecture-patterns");
}
const totalFiles = loadedGroups.reduce((sum, g) => sum + g.files.length, 0);
if (totalFiles === 0) {
throw new Error('No files found');
throw new Error("No files found");
}
console.log(` Would show ${totalFiles} files across ${loadedGroups.length} groups`);
console.log(
` Would show ${totalFiles} files across ${loadedGroups.length} groups`,
);
});
// Test 9: Verify all file examples have postSlug
test('All file examples have postSlug property', async () => {
test("All file examples have postSlug property", async () => {
const groups = await FileExampleManager.getAllGroups();
const filesWithoutPostSlug = groups.flatMap(g => g.files).filter(f => !f.postSlug);
const filesWithoutPostSlug = groups
.flatMap((g) => g.files)
.filter((f) => !f.postSlug);
if (filesWithoutPostSlug.length > 0) {
throw new Error(`${filesWithoutPostSlug.length} files missing postSlug`);
}
console.log(` All ${groups.flatMap(g => g.files).length} files have postSlug`);
console.log(
` All ${groups.flatMap((g) => g.files).length} files have postSlug`,
);
});
// Test 10: Verify postSlugs match blog post slugs
test('File example postSlugs match blog post slugs', async () => {
test("File example postSlugs match blog post slugs", async () => {
const groups = await FileExampleManager.getAllGroups();
const filePostSlugs = new Set(groups.flatMap(g => g.files).map(f => f.postSlug));
const blogPostSlugs = new Set(blogPosts.map(p => p.slug));
const filePostSlugs = new Set(
groups.flatMap((g) => g.files).map((f) => f.postSlug),
);
const blogPostSlugs = new Set(blogPosts.map((p) => p.slug));
for (const slug of filePostSlugs) {
if (slug && !blogPostSlugs.has(slug)) {
throw new Error(`File postSlug "${slug}" doesn't match any blog post`);
}
}
console.log(` All file postSlugs match blog posts`);
});
// Wait for async tests
await new Promise(resolve => setTimeout(resolve, 100));
await new Promise((resolve) => setTimeout(resolve, 100));
console.log(
`\n📊 Integration Test Results: ${passed} passed, ${failed} failed`,
);
console.log(`\n📊 Integration Test Results: ${passed} passed, ${failed} failed`);
if (failed === 0) {
console.log('🎉 All integration tests passed!');
console.log('\n✅ The file examples system is correctly integrated with blog posts!');
console.log("🎉 All integration tests passed!");
console.log(
"\n✅ The file examples system is correctly integrated with blog posts!",
);
} else {
console.log('❌ Some integration tests failed');
console.log("❌ Some integration tests failed");
process.exit(1);
}
}
// Export for use in other test files
// Export for use in other test files

View File

@@ -2,15 +2,19 @@
* Comprehensive tests for the file examples system
*/
import { FileExampleManager, sampleFileExamples, type FileExample } from '../data/fileExamples';
import {
FileExampleManager,
sampleFileExamples,
type FileExample as _FileExample,
} from "../data/fileExamples";
// Test helper to run all tests
export async function runFileExamplesTests() {
console.log('🧪 Running File Examples System Tests...\n');
console.log("🧪 Running File Examples System Tests...\n");
let passed = 0;
let failed = 0;
const test = (name: string, fn: () => void | Promise<void>) => {
try {
const result = fn();
@@ -37,62 +41,62 @@ export async function runFileExamplesTests() {
};
// Test 1: Data structure exists
test('File examples data is loaded', () => {
test("File examples data is loaded", () => {
if (!sampleFileExamples || sampleFileExamples.length === 0) {
throw new Error('No file examples found');
throw new Error("No file examples found");
}
console.log(` Found ${sampleFileExamples.length} groups`);
});
// Test 2: FileExampleManager exists
test('FileExampleManager class is available', () => {
test("FileExampleManager class is available", () => {
if (!FileExampleManager) {
throw new Error('FileExampleManager not found');
throw new Error("FileExampleManager not found");
}
});
// Test 3: Sample data has correct structure
test('Sample data has correct structure', () => {
test("Sample data has correct structure", () => {
const group = sampleFileExamples[0];
if (!group.groupId || !group.title || !Array.isArray(group.files)) {
throw new Error('Invalid group structure');
throw new Error("Invalid group structure");
}
const file = group.files[0];
if (!file.id || !file.filename || !file.content || !file.language) {
throw new Error('Invalid file structure');
throw new Error("Invalid file structure");
}
// Check for postSlug
if (!file.postSlug) {
throw new Error('Files missing postSlug property');
throw new Error("Files missing postSlug property");
}
});
// Test 4: Get all groups
test('FileExampleManager.getAllGroups() works', async () => {
test("FileExampleManager.getAllGroups() works", async () => {
const groups = await FileExampleManager.getAllGroups();
if (!Array.isArray(groups) || groups.length === 0) {
throw new Error('getAllGroups returned invalid result');
throw new Error("getAllGroups returned invalid result");
}
});
// Test 5: Get specific group
test('FileExampleManager.getGroup() works', async () => {
const group = await FileExampleManager.getGroup('python-data-processing');
test("FileExampleManager.getGroup() works", async () => {
const group = await FileExampleManager.getGroup("python-data-processing");
if (!group) {
throw new Error('Group not found');
throw new Error("Group not found");
}
if (group.groupId !== 'python-data-processing') {
throw new Error('Wrong group returned');
if (group.groupId !== "python-data-processing") {
throw new Error("Wrong group returned");
}
});
// Test 6: Search files
test('FileExampleManager.searchFiles() works', async () => {
const results = await FileExampleManager.searchFiles('python');
test("FileExampleManager.searchFiles() works", async () => {
const results = await FileExampleManager.searchFiles("python");
if (!Array.isArray(results)) {
throw new Error('searchFiles returned invalid result');
throw new Error("searchFiles returned invalid result");
}
if (results.length === 0) {
throw new Error('No results found for "python"');
@@ -100,165 +104,180 @@ export async function runFileExamplesTests() {
});
// Test 7: Get file by ID
test('FileExampleManager.getFileExample() works', async () => {
const file = await FileExampleManager.getFileExample('python-data-processor');
test("FileExampleManager.getFileExample() works", async () => {
const file = await FileExampleManager.getFileExample(
"python-data-processor",
);
if (!file) {
throw new Error('File not found');
throw new Error("File not found");
}
if (file.id !== 'python-data-processor') {
throw new Error('Wrong file returned');
if (file.id !== "python-data-processor") {
throw new Error("Wrong file returned");
}
});
// Test 8: Filter by postSlug
test('Filter files by postSlug', async () => {
test("Filter files by postSlug", async () => {
const groups = await FileExampleManager.getAllGroups();
const debuggingFiles = groups.flatMap(g => g.files).filter(f => f.postSlug === 'debugging-tips');
const debuggingFiles = groups
.flatMap((g) => g.files)
.filter((f) => f.postSlug === "debugging-tips");
if (debuggingFiles.length === 0) {
throw new Error('No files found for debugging-tips');
throw new Error("No files found for debugging-tips");
}
console.log(` Found ${debuggingFiles.length} files for debugging-tips`);
});
// Test 9: Filter by postSlug and groupId
test('Filter files by postSlug and groupId', async () => {
test("Filter files by postSlug and groupId", async () => {
const groups = await FileExampleManager.getAllGroups();
const filtered = groups
.filter(g => g.groupId === 'python-data-processing')
.map(g => ({
.filter((g) => g.groupId === "python-data-processing")
.map((g) => ({
...g,
files: g.files.filter(f => f.postSlug === 'debugging-tips')
files: g.files.filter((f) => f.postSlug === "debugging-tips"),
}))
.filter(g => g.files.length > 0);
.filter((g) => g.files.length > 0);
if (filtered.length === 0) {
throw new Error('No files found for debugging-tips in python-data-processing');
throw new Error(
"No files found for debugging-tips in python-data-processing",
);
}
console.log(` Found ${filtered[0].files.length} files`);
});
// Test 10: Filter by postSlug and tags
test('Filter files by postSlug and tags', async () => {
test("Filter files by postSlug and tags", async () => {
const groups = await FileExampleManager.getAllGroups();
const tags = ['architecture', 'design-patterns'];
const tags = ["architecture", "design-patterns"];
const filtered = groups
.map(g => ({
.map((g) => ({
...g,
files: g.files.filter(f =>
f.postSlug === 'architecture-patterns' &&
f.tags?.some(tag => tags.includes(tag))
)
files: g.files.filter(
(f) =>
f.postSlug === "architecture-patterns" &&
f.tags?.some((tag) => tags.includes(tag)),
),
}))
.filter(g => g.files.length > 0);
.filter((g) => g.files.length > 0);
if (filtered.length === 0) {
throw new Error('No files found for architecture-patterns with tags');
throw new Error("No files found for architecture-patterns with tags");
}
console.log(` Found ${filtered[0].files.length} files`);
});
// Test 11: Download single file
test('Download single file', async () => {
const result = await FileExampleManager.downloadFile('python-data-processor');
test("Download single file", async () => {
const result = await FileExampleManager.downloadFile(
"python-data-processor",
);
if (!result) {
throw new Error('Download failed');
throw new Error("Download failed");
}
if (!result.filename || !result.content || !result.mimeType) {
throw new Error('Invalid download result');
throw new Error("Invalid download result");
}
});
// Test 12: Download multiple files
test('Download multiple files', async () => {
const files = await FileExampleManager.downloadMultiple(['python-data-processor', 'python-config-example']);
test("Download multiple files", async () => {
const files = await FileExampleManager.downloadMultiple([
"python-data-processor",
"python-config-example",
]);
if (!Array.isArray(files) || files.length !== 2) {
throw new Error('Invalid multiple download result');
throw new Error("Invalid multiple download result");
}
});
// Test 13: Get available tags
test('Get available tags', async () => {
test("Get available tags", async () => {
const tags = await FileExampleManager.getAvailableTags();
if (!Array.isArray(tags) || tags.length === 0) {
throw new Error('No tags found');
throw new Error("No tags found");
}
if (!tags.includes('python') || !tags.includes('architecture')) {
throw new Error('Expected tags not found');
if (!tags.includes("python") || !tags.includes("architecture")) {
throw new Error("Expected tags not found");
}
});
// Test 14: Create new file example
test('Create new file example', async () => {
test("Create new file example", async () => {
const newExample = await FileExampleManager.createFileExample({
filename: 'test.py',
filename: "test.py",
content: 'print("test")',
language: 'python',
description: 'Test file',
tags: ['test'],
postSlug: 'test-post'
language: "python",
description: "Test file",
tags: ["test"],
postSlug: "test-post",
});
if (!newExample.id) {
throw new Error('New example has no ID');
throw new Error("New example has no ID");
}
// Verify it was added
const retrieved = await FileExampleManager.getFileExample(newExample.id);
if (!retrieved || retrieved.filename !== 'test.py') {
throw new Error('New example not found');
if (!retrieved || retrieved.filename !== "test.py") {
throw new Error("New example not found");
}
});
// Test 15: Update file example
test('Update file example', async () => {
const updated = await FileExampleManager.updateFileExample('python-data-processor', {
description: 'Updated description'
});
if (!updated || updated.description !== 'Updated description') {
throw new Error('Update failed');
test("Update file example", async () => {
const updated = await FileExampleManager.updateFileExample(
"python-data-processor",
{
description: "Updated description",
},
);
if (!updated || updated.description !== "Updated description") {
throw new Error("Update failed");
}
});
// Test 16: Delete file example
test('Delete file example', async () => {
test("Delete file example", async () => {
// First create one
const created = await FileExampleManager.createFileExample({
filename: 'delete-test.py',
content: 'test',
language: 'python',
postSlug: 'test'
filename: "delete-test.py",
content: "test",
language: "python",
postSlug: "test",
});
// Then delete it
const deleted = await FileExampleManager.deleteFileExample(created.id);
if (!deleted) {
throw new Error('Delete failed');
throw new Error("Delete failed");
}
// Verify it's gone
const retrieved = await FileExampleManager.getFileExample(created.id);
if (retrieved) {
throw new Error('File still exists after deletion');
throw new Error("File still exists after deletion");
}
});
// Wait for all async tests to complete
await new Promise(resolve => setTimeout(resolve, 100));
await new Promise((resolve) => setTimeout(resolve, 100));
console.log(`\n📊 Test Results: ${passed} passed, ${failed} failed`);
if (failed === 0) {
console.log('🎉 All tests passed!');
console.log("🎉 All tests passed!");
} else {
console.log('❌ Some tests failed');
console.log("❌ Some tests failed");
process.exit(1);
}
}
// Export for use in other test files
// Export for use in other test files