Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions src/lib/dotenv-parse.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { describe, expect, it } from "vitest";
import { appendDotenvKey, parseDotenv, removeDotenvKey } from "./dotenv-parse";

describe("parseDotenv", () => {
it("parses simple KEY=value pairs and ignores blanks and comments", () => {
expect(parseDotenv("FOO=bar\n\n# skip\nBAZ=qux")).toEqual([
{ key: "FOO", value: "bar" },
{ key: "BAZ", value: "qux" },
]);
});

it("strips optional single and double quotes from values", () => {
expect(parseDotenv(`A='x y'\nB="z"`)).toEqual([
{ key: "A", value: "x y" },
{ key: "B", value: "z" },
]);
});

it("splits on CRLF and ignores lines without a valid key", () => {
expect(parseDotenv("=nokey\r\nOK=1\n=nokey2")).toEqual([
{ key: "OK", value: "1" },
]);
});

it("parses values that contain equals signs", () => {
expect(parseDotenv("URL=https://x=y")).toEqual([
{ key: "URL", value: "https://x=y" },
]);
});
});

describe("removeDotenvKey", () => {
it("removes every assignment line for that key and preserves structure", () => {
const before = "# hdr\nFOO=1\n\nBAR=2\nFOO=3\n";
const after = removeDotenvKey(before, "FOO");
expect(after).toBe("# hdr\n\nBAR=2\n");
expect(parseDotenv(after)).toEqual([{ key: "BAR", value: "2" }]);
});

it("does not remove lines that only look like the key inside comments", () => {
const s = "# FOO=not-a-var\nBAR=1\n";
expect(removeDotenvKey(s, "FOO")).toBe(s);
});
});

describe("appendDotenvKey", () => {
it("appends a line to empty content and rejects duplicates", () => {
const first = appendDotenvKey("", "K", "v");
expect(first).toEqual({ ok: true, content: "K=v" });
const dup = appendDotenvKey(first.content!, "K", "other");

Check failure on line 50 in src/lib/dotenv-parse.test.ts

View workflow job for this annotation

GitHub Actions / check

Property 'content' does not exist on type 'AppendDotenvKeyResult'.
expect(dup).toEqual({ ok: false, error: '"K" is already defined' });
});

it("trims trailing whitespace before appending", () => {
const r = appendDotenvKey("A=1\n\n \t", "B", "2");
expect(r.ok).toBe(true);
expect(r).toEqual({ ok: true, content: "A=1\nB=2" });
});

it("quotes values that contain spaces and round-trips via parseDotenv", () => {
const r = appendDotenvKey("", "K", "a b");
expect(r.ok).toBe(true);
expect(r.content).toBe('K="a b"');

Check failure on line 63 in src/lib/dotenv-parse.test.ts

View workflow job for this annotation

GitHub Actions / check

Property 'content' does not exist on type 'AppendDotenvKeyResult'.
expect(parseDotenv(r.content!)).toEqual([{ key: "K", value: "a b" }]);

Check failure on line 64 in src/lib/dotenv-parse.test.ts

View workflow job for this annotation

GitHub Actions / check

Property 'content' does not exist on type 'AppendDotenvKeyResult'.
});

it("rejects invalid keys and multiline values with stable messages", () => {
expect(appendDotenvKey("", " ", "x")).toEqual({
ok: false,
error: "Enter a variable name",
});
expect(appendDotenvKey("", "a=b", "x")).toEqual({
ok: false,
error: 'Key cannot contain "="',
});
expect(appendDotenvKey("", "#x", "y")).toEqual({
ok: false,
error: "Key cannot start with #",
});
expect(appendDotenvKey("", "bad\nkey", "v")).toEqual({
ok: false,
error: "Key cannot span lines",
});
expect(appendDotenvKey("", "K", "a\nb")).toEqual({
ok: false,
error: "Value cannot contain line breaks",
});
});
});
22 changes: 22 additions & 0 deletions src/lib/key-token.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import { decodeObjectKeyToken, encodeObjectKeyToken } from "./key-token";

describe("encodeObjectKeyToken / decodeObjectKeyToken", () => {
it("round-trips UTF-8 object keys", () => {
const key = "vault/collection/file with spaces/émoji🔑.txt";
const token = encodeObjectKeyToken(key);
expect(token).not.toMatch(/[+/=]/);
expect(decodeObjectKeyToken(token)).toBe(key);
});

it("round-trips keys that need base64 padding when decoded", () => {
// Lengths mod 4 != 0 after URL-safe transform exercise padding branch in decode
for (const s of ["a", "ab", "abc", "prefix/object-key"]) {
expect(decodeObjectKeyToken(encodeObjectKeyToken(s))).toBe(s);
}
});

it("round-trips empty string", () => {
expect(decodeObjectKeyToken(encodeObjectKeyToken(""))).toBe("");
});
});
Loading