* Don't require auth for .ping * Finally introducing loggers * Remove file list caching from disk_space_primitives I don't think this is required anymore with the sync model * Simplification of space primitives and more tests * More reliable online status * Add .zed settings * Remove selfUpdate argument from `writeFile` no longer used - Add `sync` config schema and document sync options - Move sync config handling to client boot logic - Move BootConfig type to ui_types.ts - Remove sync env vars from server - Update documentation for sync and architecture * Improve sync logic and proxy routing for full sync state * Add BoxProxy for late-bound client and improve Lua boot - Add isMobile syscall for editor * Add concurrency to sync process and log indexing duration * Better service worker lifecycle management
228 lines
7.5 KiB
TypeScript
228 lines
7.5 KiB
TypeScript
import { assert, assertEquals } from "@std/assert";
|
|
import type { SpacePrimitives } from "./space_primitives.ts";
|
|
import type { FileMeta } from "../../type/index.ts";
|
|
import { notFoundError } from "../constants.ts";
|
|
|
|
export async function testSpacePrimitives(spacePrimitives: SpacePrimitives) {
|
|
await testBasicOperations(spacePrimitives);
|
|
await testFileOverwriting(spacePrimitives);
|
|
await testEmptyFiles(spacePrimitives);
|
|
await testUnicodeContent(spacePrimitives);
|
|
await testSpecialFileNames(spacePrimitives);
|
|
await testErrorHandling(spacePrimitives);
|
|
await testLargeFiles(spacePrimitives);
|
|
await testMetadataPreservation(spacePrimitives);
|
|
|
|
// Ensure clean state at the end
|
|
const finalFiles = await spacePrimitives.fetchFileList();
|
|
assertEquals(finalFiles, []);
|
|
}
|
|
|
|
async function testBasicOperations(spacePrimitives: SpacePrimitives) {
|
|
const files = await spacePrimitives.fetchFileList();
|
|
assertEquals(files, []);
|
|
|
|
// Write text file
|
|
const fileMeta = await spacePrimitives.writeFile(
|
|
"test.txt",
|
|
stringToBytes("Hello World"),
|
|
{
|
|
name: "test.txt",
|
|
perm: "rw",
|
|
created: 10,
|
|
contentType: "text/plain",
|
|
lastModified: 20,
|
|
size: 11,
|
|
},
|
|
);
|
|
|
|
const { data: retrievedData, meta: retrievedMeta } = await spacePrimitives
|
|
.readFile("test.txt");
|
|
|
|
assertEquals(retrievedData, stringToBytes("Hello World"));
|
|
// Check that the meta data is persisted
|
|
assertEquals(retrievedMeta.lastModified, 20);
|
|
|
|
const fbContent = (await spacePrimitives.readFile("test.txt"))
|
|
.data;
|
|
assertEquals(new TextDecoder().decode(fbContent), "Hello World");
|
|
|
|
assertEquals(await spacePrimitives.fetchFileList(), [fileMeta]);
|
|
|
|
// Write binary file
|
|
const buf = new Uint8Array(1024 * 1024);
|
|
buf.set([1, 2, 3, 4, 5]);
|
|
await spacePrimitives.writeFile("test.bin", buf);
|
|
const fileData = await spacePrimitives.readFile("test.bin");
|
|
assertEquals(fileData.data.length, 1024 * 1024);
|
|
assertEquals((await spacePrimitives.fetchFileList()).length, 2);
|
|
|
|
await spacePrimitives.deleteFile("test.bin");
|
|
assertEquals(await spacePrimitives.fetchFileList(), [fileMeta]);
|
|
|
|
// Clean up
|
|
await spacePrimitives.deleteFile("test.txt");
|
|
assertEquals(await spacePrimitives.fetchFileList(), []);
|
|
|
|
// Test weird file names
|
|
await spacePrimitives.writeFile("test+'s.txt", stringToBytes("Hello world!"));
|
|
assertEquals(
|
|
stringToBytes("Hello world!"),
|
|
(await spacePrimitives.readFile("test+'s.txt")).data,
|
|
);
|
|
await spacePrimitives.deleteFile("test+'s.txt");
|
|
|
|
// Check deletion of weird file file name
|
|
try {
|
|
await spacePrimitives.getFileMeta("test+'s.txt");
|
|
assert(false);
|
|
} catch (e: any) {
|
|
assertEquals(e, notFoundError);
|
|
}
|
|
}
|
|
|
|
async function testFileOverwriting(spacePrimitives: SpacePrimitives) {
|
|
// Test overwriting existing files
|
|
await spacePrimitives.writeFile("overwrite.txt", stringToBytes("Original"));
|
|
const _originalMeta = await spacePrimitives.getFileMeta("overwrite.txt");
|
|
|
|
await spacePrimitives.writeFile("overwrite.txt", stringToBytes("Updated"));
|
|
const updatedData = await spacePrimitives.readFile("overwrite.txt");
|
|
assertEquals(new TextDecoder().decode(updatedData.data), "Updated");
|
|
|
|
// File list should still have only one entry for this file
|
|
const filesAfterOverwrite = await spacePrimitives.fetchFileList();
|
|
const overwriteFiles = filesAfterOverwrite.filter((f) =>
|
|
f.name === "overwrite.txt"
|
|
);
|
|
assertEquals(overwriteFiles.length, 1);
|
|
|
|
await spacePrimitives.deleteFile("overwrite.txt");
|
|
}
|
|
|
|
async function testEmptyFiles(spacePrimitives: SpacePrimitives) {
|
|
// Test empty file
|
|
await spacePrimitives.writeFile("empty.txt", new Uint8Array(0));
|
|
const emptyFile = await spacePrimitives.readFile("empty.txt");
|
|
assertEquals(emptyFile.data.length, 0);
|
|
assertEquals(emptyFile.meta.size, 0);
|
|
await spacePrimitives.deleteFile("empty.txt");
|
|
}
|
|
|
|
async function testUnicodeContent(spacePrimitives: SpacePrimitives) {
|
|
// Test files with Unicode characters
|
|
const unicodeContent = "Hello 世界! 🌍 Здравствуй мир!";
|
|
await spacePrimitives.writeFile("unicode.txt", stringToBytes(unicodeContent));
|
|
const unicodeFile = await spacePrimitives.readFile("unicode.txt");
|
|
assertEquals(new TextDecoder().decode(unicodeFile.data), unicodeContent);
|
|
await spacePrimitives.deleteFile("unicode.txt");
|
|
}
|
|
|
|
async function testSpecialFileNames(spacePrimitives: SpacePrimitives) {
|
|
// Test file names with various special characters
|
|
const specialNames = [
|
|
"file with spaces.txt",
|
|
"file-with-hyphens.txt",
|
|
"file_with_underscores.txt",
|
|
"file.with.dots.txt",
|
|
"UPPERCASE.TXT",
|
|
"123numeric.txt",
|
|
"émojis🚀file.txt",
|
|
];
|
|
|
|
for (const fileName of specialNames) {
|
|
await spacePrimitives.writeFile(
|
|
fileName,
|
|
stringToBytes(`Content of ${fileName}`),
|
|
);
|
|
const fileData = await spacePrimitives.readFile(fileName);
|
|
assertEquals(
|
|
new TextDecoder().decode(fileData.data),
|
|
`Content of ${fileName}`,
|
|
);
|
|
}
|
|
|
|
// Verify all special files are in the list
|
|
const allFiles = await spacePrimitives.fetchFileList();
|
|
for (const fileName of specialNames) {
|
|
const found = allFiles.find((f) => f.name === fileName);
|
|
assert(found, `File ${fileName} should be in the file list`);
|
|
}
|
|
|
|
// Clean up special files
|
|
for (const fileName of specialNames) {
|
|
await spacePrimitives.deleteFile(fileName);
|
|
}
|
|
}
|
|
|
|
async function testErrorHandling(spacePrimitives: SpacePrimitives) {
|
|
// Test error cases
|
|
try {
|
|
await spacePrimitives.readFile("nonexistent.txt");
|
|
assert(false, "Should throw error for non-existent file");
|
|
} catch (e: any) {
|
|
assertEquals(e, notFoundError);
|
|
}
|
|
|
|
try {
|
|
await spacePrimitives.deleteFile("nonexistent.txt");
|
|
assert(false, "Should throw error when deleting non-existent file");
|
|
} catch (e: any) {
|
|
assertEquals(e, notFoundError);
|
|
}
|
|
}
|
|
|
|
async function testLargeFiles(spacePrimitives: SpacePrimitives) {
|
|
// Test large file content
|
|
const largeContent = new Uint8Array(5 * 1024 * 1024); // 5MB
|
|
for (let i = 0; i < largeContent.length; i++) {
|
|
largeContent[i] = i % 256;
|
|
}
|
|
|
|
await spacePrimitives.writeFile("large.bin", largeContent);
|
|
const largeFile = await spacePrimitives.readFile("large.bin");
|
|
assertEquals(largeFile.data.length, largeContent.length);
|
|
assertEquals(largeFile.meta.size, largeContent.length);
|
|
|
|
// Verify content integrity
|
|
for (let i = 0; i < Math.min(1000, largeContent.length); i++) {
|
|
assertEquals(largeFile.data[i], largeContent[i]);
|
|
}
|
|
|
|
await spacePrimitives.deleteFile("large.bin");
|
|
}
|
|
|
|
async function testMetadataPreservation(spacePrimitives: SpacePrimitives) {
|
|
// Test metadata preservation
|
|
const testContent = stringToBytes("Hello meta!");
|
|
const customMeta: FileMeta = {
|
|
name: "meta-test.txt",
|
|
perm: "rw",
|
|
created: 1000000,
|
|
contentType: "text/plain", // Use a content type that matches the file extension
|
|
lastModified: 2000000,
|
|
size: testContent.length, // Use actual content length
|
|
};
|
|
|
|
await spacePrimitives.writeFile(
|
|
"meta-test.txt",
|
|
testContent,
|
|
customMeta,
|
|
);
|
|
const metaFile = await spacePrimitives.readFile("meta-test.txt");
|
|
|
|
// Check that some metadata is preserved (implementations may handle timestamps differently)
|
|
assert(
|
|
metaFile.meta.lastModified > 0,
|
|
"LastModified timestamp should be set",
|
|
);
|
|
assertEquals(metaFile.meta.name, "meta-test.txt");
|
|
assertEquals(metaFile.meta.size, testContent.length);
|
|
|
|
await spacePrimitives.deleteFile("meta-test.txt");
|
|
}
|
|
|
|
function stringToBytes(str: string): Uint8Array {
|
|
return new TextEncoder().encode(str);
|
|
}
|