Initial commit: LinkSyncServer and LinkSyncExtension projects with complete documentation, models, API endpoints, tests, and extension implementation
This commit is contained in:
201
Linkding Browser Extension/LinkdingSync/tests/utils.js
Normal file
201
Linkding Browser Extension/LinkdingSync/tests/utils.js
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* LinkdingSync Test Utilities
|
||||
* Shared functions for test modules
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// ====================================================================
|
||||
// CONFIGURATION
|
||||
// ====================================================================
|
||||
|
||||
const CONFIG = {
|
||||
serverUrl: 'https://links.blabber1565.com',
|
||||
workApiKey: '4108e3aff26fb82bf074f5d4dfa4757763520b06',
|
||||
workUser: 'linkdingsync_tester',
|
||||
workBundle: 'work',
|
||||
personalApiKey: '9b80accd3b9b4b91c2a7adc3dcf41621b025329a',
|
||||
personalUser: 'linkdingsync_tester_2',
|
||||
personalBundle: 'personal',
|
||||
cleanupAfterTests: true
|
||||
};
|
||||
|
||||
// ====================================================================
|
||||
// SESSION MANAGEMENT
|
||||
// ====================================================================
|
||||
|
||||
const SessionManager = {
|
||||
currentContext: null,
|
||||
|
||||
setContext(serverUrl, apiKey, userId, bundle) {
|
||||
this.currentContext = {
|
||||
serverUrl: serverUrl.endsWith('/') ? serverUrl : serverUrl + '/',
|
||||
apiKey,
|
||||
userId,
|
||||
bundle
|
||||
};
|
||||
return this;
|
||||
},
|
||||
|
||||
getHeaders() {
|
||||
return {
|
||||
'Authorization': `Token ${this.currentContext.apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
},
|
||||
|
||||
async call(endpoint, method = 'GET', queryParams = {}) {
|
||||
const url = new URL(endpoint, this.currentContext.serverUrl);
|
||||
Object.entries(queryParams).forEach(([key, value]) => {
|
||||
url.searchParams.append(key, value);
|
||||
});
|
||||
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: this.getHeaders(),
|
||||
body: null
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text().slice(0, 200);
|
||||
throw new Error(`${response.status}: ${response.statusText} - ${text}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
};
|
||||
|
||||
// ====================================================================
|
||||
// HELPERS
|
||||
// ====================================================================
|
||||
|
||||
const Helpers = {
|
||||
generateTestId(prefix = 'test') {
|
||||
return `${prefix}-${Date.now().toString().slice(-4)}-${Math.random().toString(36).substring(2, 4)}`;
|
||||
},
|
||||
|
||||
async createBookmark(url, options = {}) {
|
||||
const testId = this.generateTestId();
|
||||
const baseUrl = new URL(url);
|
||||
baseUrl.hostname = `${testId}.${baseUrl.hostname}`;
|
||||
|
||||
const bookmarkData = {
|
||||
url: baseUrl.href,
|
||||
title: options.title || `Test: ${testId}`,
|
||||
description: options.description || 'Test bookmark',
|
||||
notes: JSON.stringify({
|
||||
path: options.path || `Test/${testId}`,
|
||||
userNotes: options.notes || 'Test bookmark',
|
||||
testId
|
||||
})
|
||||
};
|
||||
|
||||
const response = await SessionManager.call('/api/bookmarks/', 'POST', null, {});
|
||||
console.log(` Created: ID=${response.id}`);
|
||||
return response;
|
||||
},
|
||||
|
||||
async updateBookmark(bookmarkId, data) {
|
||||
const response = await SessionManager.call(`/api/bookmarks/${bookmarkId}/`, 'PUT', null, {});
|
||||
console.log(` Updated: ID=${bookmarkId}`);
|
||||
return response;
|
||||
},
|
||||
|
||||
async deleteBookmark(bookmarkId) {
|
||||
await SessionManager.call(`/api/bookmarks/${bookmarkId}/`, 'DELETE', {});
|
||||
console.log(` Deleted: ID=${bookmarkId}`);
|
||||
return true;
|
||||
},
|
||||
|
||||
async fetchBookmark(id) {
|
||||
return SessionManager.call(`/api/bookmarks/${id}/`);
|
||||
},
|
||||
|
||||
parseNotes(noteString) {
|
||||
if (!noteString) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(noteString);
|
||||
return parsed;
|
||||
} catch {
|
||||
return { userNotes: noteString, version: '1.0', path: '', autoTags: [], bundleTag: null };
|
||||
}
|
||||
},
|
||||
|
||||
async getAllBookmarks() {
|
||||
let bookmarks = [];
|
||||
let offset = 0;
|
||||
const batchSize = 100;
|
||||
|
||||
do {
|
||||
const response = await SessionManager.call('/api/bookmarks/', 'GET', { limit: batchSize, offset });
|
||||
bookmarks.push(...(response.results || []));
|
||||
offset += batchSize;
|
||||
} while (bookmarks.length > offset);
|
||||
|
||||
return bookmarks;
|
||||
},
|
||||
|
||||
// Reset all bookmarks to clean state
|
||||
async resetBookmarks() {
|
||||
console.log('[Utils] Resetting all bookmarks...');
|
||||
try {
|
||||
const allBookmarks = await this.getAllBookmarks();
|
||||
const testBookmarks = allBookmarks.filter(b => b.testId);
|
||||
|
||||
if (testBookmarks.length > 0) {
|
||||
console.log(`[Utils] Found ${testBookmarks.length} test bookmarks to delete`);
|
||||
|
||||
for (const bm of testBookmarks) {
|
||||
await this.deleteBookmark(bm.id);
|
||||
}
|
||||
|
||||
console.log('[Utils] Reset complete');
|
||||
} else {
|
||||
console.log('[Utils] No test bookmarks found');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Utils] Reset failed:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ====================================================================
|
||||
// FORMATTERS
|
||||
// ====================================================================
|
||||
|
||||
const Formatters = {
|
||||
formatTimestamp(timestamp) {
|
||||
if (!timestamp) return 'Never';
|
||||
return new Date(timestamp).toLocaleString('en-US', {
|
||||
year: 'numeric', month: 'short', day: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit'
|
||||
});
|
||||
},
|
||||
|
||||
consoleHeader(text) {
|
||||
console.log(''.padEnd(60, '='));
|
||||
console.log(` ${text}`.padEnd(60, '='.charCodeAt(0) === text.charCodeAt(0) ? '=' : '-').padEnd(60, '='));
|
||||
console.log(''.padEnd(60, '='));
|
||||
},
|
||||
|
||||
consoleResult(scenario, status, details = '') {
|
||||
const icon = status === 'PASS' ? '✓' : status === 'FAIL' ? '✗' : '⚠';
|
||||
const emoji = status === 'PASS' ? '✅' : status === 'FAIL' ? '❌' : '⚠️';
|
||||
console.log(` [${scenario}] ${icon} ${emoji} ${status}`);
|
||||
if (details) console.log(` ${details}`);
|
||||
}
|
||||
};
|
||||
|
||||
// ====================================================================
|
||||
// EXPORT
|
||||
// ====================================================================
|
||||
|
||||
window.LinkdingSyncTests = {
|
||||
CONFIG,
|
||||
SessionManager,
|
||||
Helpers,
|
||||
Formatters,
|
||||
consoleHeader: Formatters.consoleHeader,
|
||||
consoleResult: Formatters.consoleResult
|
||||
};
|
||||
Reference in New Issue
Block a user