CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | /**
* logActivity() — the activity_feed writer wired into push/PR/merge paths
* on 2026-07-15 (see src/hooks/post-receive.ts, src/routes/pulls.tsx,
* src/lib/pr-merge.ts, src/lib/mcp-tools.ts). Before this, activity_feed
* was only ever written by fork/use-template/claude-connect/reviewer-suggest,
* so dashboards and the repo-page "recent push" indicator were blind to
* anything done through git push, the web PR flow, or the MCP write tools.
*
* Uses the same mock.module("../db", ...) fake-db pattern as push.test.ts —
* `logActivity` must never throw (mirrors `audit()`'s contract), and it
* must write the exact shape callers rely on (action, targetType, targetId,
* metadata as a JSON string).
*/
import { describe, it, expect, beforeEach, afterAll, mock } from "bun:test";
const _real_db = await import("../db");
const _inserted: Array<{ table: string; values: any }> = [];
let _shouldThrow = false;
const tableName = (t: any): string => {
if (!t || typeof t !== "object") return "?";
if ("action" in t && "repositoryId" in t && "targetType" in t) return "activity_feed";
return "?";
};
const _fakeDb = {
db: {
insert: (t: any) => ({
values: (vals: any) => {
if (_shouldThrow) return Promise.reject(new Error("db unavailable"));
_inserted.push({ table: tableName(t), values: vals });
return Promise.resolve();
},
}),
},
getDb: () => _fakeDb.db,
};
mock.module("../db", () => ({ ..._real_db, ..._fakeDb }));
const { logActivity } = await import("../lib/notify");
afterAll(() => {
mock.module("../db", () => _real_db);
});
beforeEach(() => {
_inserted.length = 0;
_shouldThrow = false;
});
describe("logActivity", () => {
it("writes an activity_feed row with the given action/target/metadata", async () => {
await logActivity({
repositoryId: "repo-1",
userId: "user-1",
action: "push",
targetType: "commit",
targetId: "a".repeat(40),
metadata: { branch: "main" },
});
expect(_inserted).toHaveLength(1);
expect(_inserted[0]!.table).toBe("activity_feed");
expect(_inserted[0]!.values).toMatchObject({
repositoryId: "repo-1",
userId: "user-1",
action: "push",
targetType: "commit",
targetId: "a".repeat(40),
});
expect(_inserted[0]!.values.metadata).toBe(JSON.stringify({ branch: "main" }));
});
it("defaults userId to null when omitted", async () => {
await logActivity({ repositoryId: "repo-1", action: "pr_open" });
expect(_inserted[0]!.values.userId).toBeNull();
});
it("never throws when the DB insert fails — same fail-open contract as audit()", async () => {
_shouldThrow = true;
await expect(
logActivity({ repositoryId: "repo-1", action: "pr_merge" })
).resolves.toBeUndefined();
expect(_inserted).toHaveLength(0);
});
it("sets metadata to null when not provided", async () => {
await logActivity({ repositoryId: "repo-1", action: "comment", targetType: "pull_request", targetId: "42" });
expect(_inserted[0]!.values.metadata).toBeNull();
});
});
|