From 0e3d7a17e097eca65a72b0b1e01b159a650a94c2 Mon Sep 17 00:00:00 2001 From: Johannes Laurin Hoermann Date: Tue, 24 Mar 2026 21:18:46 +0900 Subject: [PATCH 1/3] MAINT: add MIT LICENSE file --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..967e5de --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 livMatS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 5e0657e28d7f663127ad3ab876684628352de906 Mon Sep 17 00:00:00 2001 From: Johannes Laurin Hoermann Date: Tue, 24 Mar 2026 21:23:48 +0900 Subject: [PATCH 2/3] TST: add vitest unit tests for utility functions --- src/__tests__/utils.test.ts | 157 ++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/__tests__/utils.test.ts diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts new file mode 100644 index 0000000..3f7726f --- /dev/null +++ b/src/__tests__/utils.test.ts @@ -0,0 +1,157 @@ +import { describe, it, expect } from "vitest"; +import { + generateUUID, + encodeUri, + getCurrentTimestamp, + parseISOTimestamp, + isExpired, + timeUntilExpiry, + formatBytes, + chunk, + generateIdentifier, +} from "../utils"; + +describe("generateUUID", () => { + it("returns a string", () => { + expect(typeof generateUUID()).toBe("string"); + }); + + it("returns a UUID v4 format", () => { + const uuid = generateUUID(); + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + expect(uuid).toMatch(uuidRegex); + }); + + it("returns different values on successive calls", () => { + const uuid1 = generateUUID(); + const uuid2 = generateUUID(); + expect(uuid1).not.toBe(uuid2); + }); +}); + +describe("encodeUri", () => { + it("encodes spaces in URI", () => { + const result = encodeUri("s3://my-bucket/my dataset"); + expect(result).not.toContain(" "); + }); + + it("encodes slashes", () => { + const result = encodeUri("s3://bucket/path/to/file"); + expect(result).not.toContain("/"); + }); + + it("returns a non-empty string", () => { + expect(encodeUri("s3://bucket/dataset")).toBeTruthy(); + }); +}); + +describe("generateIdentifier", () => { + it("returns a string", async () => { + const id = await generateIdentifier("data/file.txt"); + expect(typeof id).toBe("string"); + expect(id.length).toBeGreaterThan(0); + }); + + it("is deterministic for the same input", async () => { + const id1 = await generateIdentifier("data/file.txt"); + const id2 = await generateIdentifier("data/file.txt"); + expect(id1).toBe(id2); + }); + + it("produces different identifiers for different paths", async () => { + const id1 = await generateIdentifier("data/file1.txt"); + const id2 = await generateIdentifier("data/file2.txt"); + expect(id1).not.toBe(id2); + }); + + it("returns a hex string (SHA-1 = 40 chars)", async () => { + const id = await generateIdentifier("data/file.txt"); + expect(id).toMatch(/^[0-9a-f]{40}$/); + }); +}); + +describe("getCurrentTimestamp", () => { + it("returns a number", () => { + expect(typeof getCurrentTimestamp()).toBe("number"); + }); + + it("returns a Unix timestamp close to now", () => { + const ts = getCurrentTimestamp(); + const now = Math.floor(Date.now() / 1000); + expect(Math.abs(ts - now)).toBeLessThan(2); + }); +}); + +describe("parseISOTimestamp", () => { + it("parses a known ISO timestamp", () => { + const ts = parseISOTimestamp("2024-01-01T00:00:00.000Z"); + expect(ts).toBe(1704067200); + }); + + it("returns a number", () => { + expect(typeof parseISOTimestamp("2024-01-01T00:00:00Z")).toBe("number"); + }); +}); + +describe("isExpired", () => { + it("returns true for a past timestamp", () => { + expect(isExpired("2000-01-01T00:00:00Z")).toBe(true); + }); + + it("returns false for a future timestamp", () => { + const future = new Date(Date.now() + 3600 * 1000).toISOString(); + expect(isExpired(future)).toBe(false); + }); +}); + +describe("timeUntilExpiry", () => { + it("returns 0 for a past timestamp", () => { + expect(timeUntilExpiry("2000-01-01T00:00:00Z")).toBe(0); + }); + + it("returns a positive number for a future timestamp", () => { + const future = new Date(Date.now() + 3600 * 1000).toISOString(); + expect(timeUntilExpiry(future)).toBeGreaterThan(0); + }); +}); + +describe("formatBytes", () => { + it("formats 0 bytes", () => { + expect(formatBytes(0)).toBe("0 Bytes"); + }); + + it("formats kilobytes", () => { + expect(formatBytes(1024)).toBe("1 KB"); + }); + + it("formats megabytes", () => { + expect(formatBytes(1024 * 1024)).toBe("1 MB"); + }); + + it("formats gigabytes with decimals", () => { + const result = formatBytes(1.5 * 1024 * 1024 * 1024); + expect(result).toContain("GB"); + }); +}); + +describe("chunk", () => { + it("chunks an array into equal parts", () => { + const result = chunk([1, 2, 3, 4, 5, 6], 2); + expect(result).toEqual([[1, 2], [3, 4], [5, 6]]); + }); + + it("handles arrays not evenly divisible", () => { + const result = chunk([1, 2, 3, 4, 5], 2); + expect(result).toEqual([[1, 2], [3, 4], [5]]); + }); + + it("returns single chunk when size >= array length", () => { + const result = chunk([1, 2, 3], 10); + expect(result).toEqual([[1, 2, 3]]); + }); + + it("returns empty array for empty input", () => { + expect(chunk([], 3)).toEqual([]); + }); +}); From fdd80295f6b75583ae909d7e85fd7df7a4dd8cd7 Mon Sep 17 00:00:00 2001 From: Johannes Laurin Hoermann Date: Tue, 24 Mar 2026 21:26:19 +0900 Subject: [PATCH 3/3] MAINT: add GitHub Actions CI workflow --- .github/workflows/test.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c05f22b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,22 @@ +name: test + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: ['18', '20', '22'] + steps: + - uses: actions/checkout@v4 + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: npm install + - name: Build + run: npm run build + - name: Test + run: npm test