diff --git a/lith/deploy.fish b/lith/deploy.fish index e06336d847..0e1f2def65 100644 --- a/lith/deploy.fish +++ b/lith/deploy.fish @@ -335,6 +335,9 @@ end rm -f $TMP_SPACES # Install service file + Caddy config from the deployed checkout +# systemctl reload uses --force, rebuilding TLS listeners even for identical +# config. Ordinary code deploys should leave them running; caddy reload skips +# unchanged config. Certificate-only rotations still need an explicit --force. echo -e "$GREEN-> Updating service + Caddy config...$NC" ssh -i $SSH_KEY $LITH_USER@$TARGET_HOST "\ cp $REMOTE_DIR/lith/lith.service /etc/systemd/system/lith.service && \ @@ -349,7 +352,7 @@ if [ ! -f /var/lib/aesthetic-computer/gym.anthonyzollo.com/index.html ]; then \ cp $REMOTE_DIR/lith/gym/index.html /var/lib/aesthetic-computer/gym.anthonyzollo.com/index.html; \ fi && \ systemctl daemon-reload && \ -systemctl reload caddy" +caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile" # Restart lith service, and the Amail SMTP door beside it echo -e "$GREEN-> Restarting lith + lith-mail...$NC" diff --git a/system/netlify/functions/mood.mjs b/system/netlify/functions/mood.mjs index d7f7d2ceff..6a1cc043a0 100644 --- a/system/netlify/functions/mood.mjs +++ b/system/netlify/functions/mood.mjs @@ -172,9 +172,7 @@ export async function handler(event, context) { // them as { moods }. const moods = await allMoods(database, handle); await database.disconnect(); - return moods && moods.length > 0 - ? respond(200, { moods }) - : respond(500, { message: "No mood found." }); + return respond(200, { moods }); } else if (slug === "search") { // GET /api/mood/search?q=&limit=N // Case-insensitive substring search over recent moods, newest first, diff --git a/system/tests/production-endpoints.test.mjs b/system/tests/production-endpoints.test.mjs index d9dbad3890..f26e015571 100644 --- a/system/tests/production-endpoints.test.mjs +++ b/system/tests/production-endpoints.test.mjs @@ -30,6 +30,42 @@ async function load(name, { env = {}, mocks = {}, ...options } = {}) { return module.namespace.handler; } +test("mood lists return a successful empty collection for handles without moods", async () => { + let records = [], failure, disconnected = 0; + const database = { disconnect: async () => { disconnected++; } }; + const unused = () => { throw Error("Unexpected dependency call"); }; + const handler = await load("mood.mjs", { mocks: { + "../../backend/authorization.mjs": { authorize: unused, userIDFromHandleOrEmail: unused, getHandleOrEmail: unused }, + "../../backend/database.mjs": { + connect: async () => database, moodFor: unused, getMoodByRkey: unused, + allMoods: async (db, handle) => { + assert.equal(db, database); assert.equal(handle, "@fixture"); + if (failure) throw failure; + return records; + }, + }, + "../../backend/http.mjs": http, + "../../backend/mood-atproto.mjs": { createMoodOnAtproto: unused }, + "../../backend/bluesky-mirror.mjs": { shouldMirror: unused, postMoodToBluesky: unused }, + "../../backend/bluesky-engagement.mjs": { fetchBlueskyEngagement: unused }, + "../../../shared/push.mjs": { broadcastToTopic: unused }, + "../../backend/shell.mjs": { shell: {} }, + "../../backend/profile-stream.mjs": { publishProfileEvent: unused }, + "../../backend/filter.mjs": { filter: unused }, + } }); + const event = { httpMethod: "GET", path: "/api/mood/all", queryStringParameters: { for: "@fixture" } }; + const empty = await handler(event); + assert.equal(empty.statusCode, 200); + assert.deepEqual(JSON.parse(empty.body), { moods: [] }); + records = [{ handle: "@fixture", mood: "hello", when: "2026-10-08T12:00:00Z" }]; + const populated = await handler(event); + assert.equal(populated.statusCode, 200); + assert.deepEqual(JSON.parse(populated.body), { moods: records }); + assert.equal(disconnected, 2); + failure = Error("Database unavailable"); + await assert.rejects(handler(event), /Database unavailable/); +}); + for (const faster of ["lookup", "gallery"]) test(`painting metadata ${faster} completion preserves another in-flight lookup`, async () => { let closed = false, entered, release; const started = new Promise(resolve => { entered = resolve; });