gitforest 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +24 -4
  3. package/src/components/onboarding/DirectoriesStep.tsx +19 -19
  4. package/src/github/auth.ts +3 -3
  5. package/src/utils/debug.ts +4 -4
  6. package/.bunignore +0 -7
  7. package/.github/workflows/ci.yml +0 -73
  8. package/CLAUDE.md +0 -111
  9. package/CONTRIBUTING.md +0 -145
  10. package/bun.lock +0 -267
  11. package/bunfig.toml +0 -15
  12. package/cli +0 -0
  13. package/docs/ai/IMPROVEMENT_PLAN.md +0 -341
  14. package/docs/ai/VERIFICATION_REPORT.md +0 -87
  15. package/docs/ai/architecture.md +0 -169
  16. package/docs/ai/checks/check-2025-12-02-tests.md +0 -40
  17. package/docs/ai/checks/check-2025-12-02.md +0 -55
  18. package/docs/ai/checks/test-verification-report.md +0 -85
  19. package/docs/ai/implementation-guide.md +0 -776
  20. package/docs/ai/research/gitty-codebase-analysis.md +0 -221
  21. package/docs/ai/tickets/GENERAL-sitrep.md +0 -30
  22. package/docs/ai/tickets/TASK-database-tests-sitrep.md +0 -25
  23. package/docs/ai/tickets/TASK-deprecated-functions-sitrep.md +0 -28
  24. package/docs/ai/tickets/TASK-detail-modal-sitrep.md +0 -28
  25. package/docs/ai/tickets/TASK-filter-overlay-sitrep.md +0 -24
  26. package/docs/ai/tickets/TASK-github-service-sitrep.md +0 -32
  27. package/docs/ai/tickets/TASK-github-token-sitrep.md +0 -51
  28. package/docs/ai/tickets/TASK-hascommits-sitrep.md +0 -35
  29. package/docs/ai/tickets/TASK-keybindings-sitrep.md +0 -26
  30. package/docs/ai/tickets/TASK-layout-sitrep.md +0 -25
  31. package/docs/ai/tickets/TASK-markdown-sitrep.md +0 -28
  32. package/docs/ai/tickets/TASK-project-item-sitrep.md +0 -79
  33. package/docs/ai/tickets/TASK-sitrep.md +0 -28
  34. package/docs/ai/tickets/TASK-state-sitrep.md +0 -26
  35. package/docs/ai/tickets/TASK-types-sitrep.md +0 -25
  36. package/docs/ai/tickets/TASK-unified-item-fix-sitrep.md +0 -26
  37. package/docs/ai/tickets/TKT-001-sitrep.md +0 -24
  38. package/docs/ai/tickets/TKT-002-sitrep.md +0 -25
  39. package/docs/ai/tickets/TKT-003-git-service-refactoring-complete.md +0 -46
  40. package/docs/ai/tickets/TKT-003-git-service-refactoring-plan.md +0 -135
  41. package/docs/ai/tickets/TKT-003-sitrep.md +0 -26
  42. package/docs/ai/tickets/TKT-004-sitrep.md +0 -27
  43. package/docs/ai/tickets/TKT-005-sitrep.md +0 -25
  44. package/docs/ai/tickets/TKT-006-sitrep.md +0 -26
  45. package/docs/ai/tickets/TKT-007-sitrep.md +0 -30
  46. package/docs/ai/tickets/TKT-008-sitrep.md +0 -32
  47. package/docs/ai/tickets/TKT-009-sitrep.md +0 -27
  48. package/docs/ai/tickets/TKT-010-sitrep.md +0 -27
  49. package/docs/ai/tickets/TKT-011-sitrep.md +0 -26
  50. package/docs/ai/tickets/TKT-012-sitrep.md +0 -25
  51. package/docs/ai/tickets/sitreps/TASK-actions-sitrep.md +0 -28
  52. package/docs/ai/tickets/sitreps/TASK-actions-test-sitrep.md +0 -25
  53. package/docs/ai/tickets/sitreps/TASK-app-integration-sitrep.md +0 -25
  54. package/docs/ai/tickets/sitreps/TASK-background-fetch-sitrep.md +0 -24
  55. package/docs/ai/tickets/sitreps/TASK-background-fetch-test-sitrep.md +0 -29
  56. package/docs/ai/tickets/sitreps/TASK-batch-tests-sitrep.md +0 -29
  57. package/docs/ai/tickets/sitreps/TASK-bun-test-sitrep.md +0 -26
  58. package/docs/ai/tickets/sitreps/TASK-cache-tests-sitrep.md +0 -30
  59. package/docs/ai/tickets/sitreps/TASK-cli-tests-sitrep.md +0 -28
  60. package/docs/ai/tickets/sitreps/TASK-clone-error-handling-sitrep.md +0 -26
  61. package/docs/ai/tickets/sitreps/TASK-commands-tests-sitrep.md +0 -25
  62. package/docs/ai/tickets/sitreps/TASK-component-tests-1-sitrep.md +0 -30
  63. package/docs/ai/tickets/sitreps/TASK-configloader-tests-sitrep.md +0 -25
  64. package/docs/ai/tickets/sitreps/TASK-confirm-dialog-test-sitrep.md +0 -29
  65. package/docs/ai/tickets/sitreps/TASK-coverage-sitrep.md +0 -95
  66. package/docs/ai/tickets/sitreps/TASK-database-tests-summary.md +0 -61
  67. package/docs/ai/tickets/sitreps/TASK-error-boundary-sitrep.md +0 -30
  68. package/docs/ai/tickets/sitreps/TASK-error-tests-sitrep.md +0 -27
  69. package/docs/ai/tickets/sitreps/TASK-errors-tests-sitrep.md +0 -25
  70. package/docs/ai/tickets/sitreps/TASK-extract-reducer-sitrep.md +0 -27
  71. package/docs/ai/tickets/sitreps/TASK-filter-overlay-test-sitrep.md +0 -25
  72. package/docs/ai/tickets/sitreps/TASK-final-verification-sitrep.md +0 -28
  73. package/docs/ai/tickets/sitreps/TASK-fix-all-tests-sitrep.md +0 -25
  74. package/docs/ai/tickets/sitreps/TASK-fix-hooks-sitrep.md +0 -26
  75. package/docs/ai/tickets/sitreps/TASK-fix-remaining-tests-sitrep.md +0 -25
  76. package/docs/ai/tickets/sitreps/TASK-fix-test-failures-sitrep.md +0 -26
  77. package/docs/ai/tickets/sitreps/TASK-fix-tests-sitrep.md +0 -24
  78. package/docs/ai/tickets/sitreps/TASK-formatters-tests-sitrep.md +0 -25
  79. package/docs/ai/tickets/sitreps/TASK-git-timeouts-sitrep.md +0 -29
  80. package/docs/ai/tickets/sitreps/TASK-github-cache-test-sitrep.md +0 -25
  81. package/docs/ai/tickets/sitreps/TASK-githubcli-tests-sitrep.md +0 -24
  82. package/docs/ai/tickets/sitreps/TASK-gitstatus-tests-sitrep.md +0 -24
  83. package/docs/ai/tickets/sitreps/TASK-hooks-isolation-sitrep.md +0 -27
  84. package/docs/ai/tickets/sitreps/TASK-keybindings-tests-sitrep.md +0 -25
  85. package/docs/ai/tickets/sitreps/TASK-layout-tests-sitrep.md +0 -25
  86. package/docs/ai/tickets/sitreps/TASK-mock-factories-sitrep.md +0 -27
  87. package/docs/ai/tickets/sitreps/TASK-modal-tests-sitrep.md +0 -32
  88. package/docs/ai/tickets/sitreps/TASK-processbatch-fix-sitrep.md +0 -27
  89. package/docs/ai/tickets/sitreps/TASK-projectlist-tests-sitrep.md +0 -30
  90. package/docs/ai/tickets/sitreps/TASK-projectutils-tests-sitrep.md +0 -25
  91. package/docs/ai/tickets/sitreps/TASK-scanner-tests-sitrep.md +0 -29
  92. package/docs/ai/tickets/sitreps/TASK-select-all-sitrep.md +0 -25
  93. package/docs/ai/tickets/sitreps/TASK-shell-error-handling-sitrep.md +0 -27
  94. package/docs/ai/tickets/sitreps/TASK-store-tests-sitrep.md +0 -25
  95. package/docs/ai/tickets/sitreps/TASK-test-fixes-sitrep.md +0 -26
  96. package/docs/ai/tickets/sitreps/TASK-test-summary-sitrep.md +0 -25
  97. package/docs/ai/tickets/sitreps/TASK-test-verification-sitrep.md +0 -27
  98. package/docs/ai/tickets/sitreps/TASK-testsuite-sitrep.md +0 -75
  99. package/docs/ai/tickets/sitreps/TASK-unified-reducer-tests-sitrep.md +0 -29
  100. package/docs/ai/tickets/sitreps/TASK-unified-repos-test-sitrep.md +0 -29
  101. package/docs/ai/tickets/sitreps/TASK-unified-tests-sitrep.md +0 -25
  102. package/docs/ai/tickets/sitreps/TASK-useprojects-tests-sitrep.md +0 -25
  103. package/docs/ai/tickets/sitreps/TASK-utility-tests-sitrep.md +0 -32
  104. package/docs/ai/tickets/sitreps/TKT-003-git-service-refactoring-sitrep.md +0 -64
  105. package/docs/ai/tkt-001-fix-database-error.md +0 -217
  106. package/docs/ai/ui-enhancement-plan.md +0 -562
  107. package/test/integration/app.isolated.tsx +0 -240
  108. package/test/integration/cli-commands.test.ts +0 -287
  109. package/test/integration/cli-validation.test.ts +0 -264
  110. package/test/integration/git-operations.test.ts +0 -218
  111. package/test/integration/scanner.test.ts +0 -228
  112. package/test/preload.ts +0 -18
  113. package/test/unit/cli/commands.test.ts +0 -13
  114. package/test/unit/cli/formatters.test.ts +0 -1116
  115. package/test/unit/cli/github-commands.test.ts +0 -12
  116. package/test/unit/components/CloneDialog.test.tsx +0 -240
  117. package/test/unit/components/ColumnHeader.test.tsx +0 -128
  118. package/test/unit/components/CommandPalette.test.tsx +0 -355
  119. package/test/unit/components/ConfirmDialog.test.tsx +0 -111
  120. package/test/unit/components/ErrorBoundary.test.tsx +0 -139
  121. package/test/unit/components/FilterBar.test.tsx +0 -43
  122. package/test/unit/components/FilterOptionsOverlay.test.tsx +0 -197
  123. package/test/unit/components/HelpOverlay.test.tsx +0 -90
  124. package/test/unit/components/Layout.test.tsx +0 -328
  125. package/test/unit/components/MarkdownRenderer.test.tsx +0 -45
  126. package/test/unit/components/ProgressBar.test.tsx +0 -138
  127. package/test/unit/components/ProjectItem.test.tsx +0 -182
  128. package/test/unit/components/ProjectList.test.tsx +0 -311
  129. package/test/unit/components/RepoDetailModal.test.tsx +0 -445
  130. package/test/unit/components/StatusBar.test.tsx +0 -112
  131. package/test/unit/components/UnifiedProjectItem.test.tsx +0 -618
  132. package/test/unit/components/ViewModeIndicator.test.tsx +0 -137
  133. package/test/unit/components/test-utils.tsx +0 -63
  134. package/test/unit/config/loader.test.ts +0 -692
  135. package/test/unit/db/database.test.ts +0 -978
  136. package/test/unit/db/index.test.ts +0 -314
  137. package/test/unit/fixtures/setup.ts +0 -186
  138. package/test/unit/git/commands-untested.test.ts +0 -205
  139. package/test/unit/git/commands.test.ts +0 -269
  140. package/test/unit/git/operations.test.ts +0 -322
  141. package/test/unit/git/status.test.ts +0 -219
  142. package/test/unit/github/auth.test.ts +0 -317
  143. package/test/unit/github/cache.test.ts +0 -1028
  144. package/test/unit/github/cli.test.ts +0 -135
  145. package/test/unit/github/unified.test.ts +0 -1201
  146. package/test/unit/graceful-shutdown.test.ts +0 -83
  147. package/test/unit/hooks/useBackgroundFetch.test.tsx +0 -239
  148. package/test/unit/hooks/useConfirmDialogActions.test.tsx +0 -81
  149. package/test/unit/hooks/useKeyBindings.isolated.ts +0 -715
  150. package/test/unit/hooks/useProjects.test.tsx +0 -186
  151. package/test/unit/hooks/useUnifiedRepos-simple.test.tsx +0 -115
  152. package/test/unit/hooks/useUnifiedRepos.test.tsx +0 -177
  153. package/test/unit/mocks/config.ts +0 -109
  154. package/test/unit/mocks/git-service.ts +0 -274
  155. package/test/unit/mocks/github-service.ts +0 -250
  156. package/test/unit/mocks/index.ts +0 -72
  157. package/test/unit/mocks/project.ts +0 -148
  158. package/test/unit/mocks/state-mocks.ts +0 -187
  159. package/test/unit/mocks/unified.ts +0 -169
  160. package/test/unit/operations/batch.test.ts +0 -216
  161. package/test/unit/operations/commands.test.ts +0 -550
  162. package/test/unit/scanner/errors.test.ts +0 -297
  163. package/test/unit/scanner/index.test.ts +0 -1011
  164. package/test/unit/scanner/markers.test.ts +0 -150
  165. package/test/unit/scanner/submodules.test.ts +0 -99
  166. package/test/unit/services/git-errors.test.ts +0 -190
  167. package/test/unit/services/git.test.ts +0 -442
  168. package/test/unit/services/github-errors.test.ts +0 -293
  169. package/test/unit/services/github.test.ts +0 -200
  170. package/test/unit/state/actions.test.ts +0 -217
  171. package/test/unit/state/reducer.test.ts +0 -745
  172. package/test/unit/state/store.test.tsx +0 -711
  173. package/test/unit/types/commands.test.ts +0 -220
  174. package/test/unit/types/schema.test.ts +0 -179
  175. package/test/unit/utils/array.test.ts +0 -73
  176. package/test/unit/utils/debug.test.ts +0 -23
  177. package/test/unit/utils/errors.test.ts +0 -295
  178. package/test/unit/utils/markdown.test.ts +0 -163
  179. package/test/unit/utils/project-utils.test.ts +0 -756
  180. package/test/unit/utils/rate-limiter.test.ts +0 -256
  181. package/test/unit/utils/retry.test.ts +0 -165
  182. package/test/unit/utils/strip-ansi.ts +0 -13
  183. package/test/unit/utils/timeout.test.ts +0 -93
  184. package/tsconfig.json +0 -29
@@ -1,314 +0,0 @@
1
- /**
2
- * Tests for database module functions
3
- * Tests the actual exported functions from src/db/index.ts
4
- */
5
-
6
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
7
- import { join } from "path";
8
- import { existsSync, rmSync } from "fs";
9
- import { eq } from "drizzle-orm";
10
- import * as dbModule from "../../../src/db/index.ts";
11
-
12
- describe("Database Module Functions", () => {
13
- let originalEnv: Record<string, string | undefined>;
14
- let tempDir: string;
15
-
16
- beforeEach(() => {
17
- // Save original environment
18
- originalEnv = { ...process.env };
19
-
20
- // Create a temporary directory for test databases
21
- tempDir = `/tmp/gitforest-test-${Date.now()}`;
22
- });
23
-
24
- afterEach(() => {
25
- // Restore original environment
26
- process.env = originalEnv;
27
-
28
- // Clean up temp directory
29
- if (existsSync(tempDir)) {
30
- rmSync(tempDir, { recursive: true, force: true });
31
- }
32
-
33
- // Close any open database connections
34
- try {
35
- dbModule.closeDb();
36
- } catch {
37
- // Ignore errors during cleanup
38
- }
39
- });
40
-
41
- describe("getDbPath", () => {
42
- test("returns correct path with XDG_DATA_HOME set", () => {
43
- process.env.XDG_DATA_HOME = "/custom/data/home";
44
-
45
- const result = dbModule.getDbPath();
46
-
47
- expect(result).toBe(join("/custom/data/home", "gitforest", "cache.db"));
48
- });
49
-
50
- test("returns default path without XDG_DATA_HOME", () => {
51
- // Remove XDG_DATA_HOME if it exists
52
- delete process.env.XDG_DATA_HOME;
53
-
54
- const result = dbModule.getDbPath();
55
-
56
- // Should use homedir() + .local/share/gitforest/cache.db
57
- expect(result).toContain(".local");
58
- expect(result).toContain("share");
59
- expect(result).toContain("gitforest");
60
- expect(result).toContain("cache.db");
61
- });
62
-
63
- test("constructs path correctly on different platforms", () => {
64
- // This test verifies the path construction logic
65
- // The actual homedir() value depends on the test environment
66
- delete process.env.XDG_DATA_HOME;
67
-
68
- const result = dbModule.getDbPath();
69
-
70
- expect(result).toMatch(/.*gitforest.*cache\.db$/);
71
- expect(result).not.toContain("undefined");
72
- });
73
- });
74
-
75
- describe("initDb", () => {
76
- test("creates database file and directory", async () => {
77
- // Set a custom temp directory for the database
78
- process.env.XDG_DATA_HOME = tempDir;
79
-
80
- const db = await dbModule.initDb();
81
-
82
- expect(db).toBeDefined();
83
-
84
- // Verify database file was created
85
- const dbPath = dbModule.getDbPath();
86
- expect(existsSync(dbPath)).toBe(true);
87
-
88
- // Verify directory was created
89
- const dbDir = join(dbPath, "..");
90
- expect(existsSync(dbDir)).toBe(true);
91
- });
92
-
93
- test("enables WAL mode", async () => {
94
- process.env.XDG_DATA_HOME = tempDir;
95
-
96
- await dbModule.initDb();
97
-
98
- // WAL mode is enabled via PRAGMA, we can't easily test it directly
99
- // but we can verify the database was created successfully
100
- const dbPath = dbModule.getDbPath();
101
- expect(existsSync(dbPath)).toBe(true);
102
- });
103
-
104
- test("creates all required tables", async () => {
105
- process.env.XDG_DATA_HOME = tempDir;
106
-
107
- const db = await dbModule.initDb();
108
-
109
- // Try to query each table to verify they exist
110
- const tables = [
111
- "projects",
112
- "remote_status",
113
- "github_repos",
114
- "config_cache"
115
- ];
116
-
117
- for (const table of tables) {
118
- // This should not throw if table exists
119
- const result = db.run(`SELECT 1 FROM ${table} LIMIT 1`);
120
- expect(result).toBeDefined();
121
- }
122
- });
123
-
124
- test("returns same instance on repeated calls (singleton)", async () => {
125
- process.env.XDG_DATA_HOME = tempDir;
126
-
127
- const db1 = await dbModule.initDb();
128
- const db2 = await dbModule.initDb();
129
-
130
- expect(db1).toBe(db2);
131
- });
132
-
133
- test("handles directory creation errors", async () => {
134
- // Create a file where we want to create a directory
135
- const conflictingPath = join(tempDir, "gitforest");
136
- process.env.XDG_DATA_HOME = tempDir;
137
-
138
- // Create a file with the same name as the directory we want
139
- await Bun.write(conflictingPath, "conflict");
140
-
141
- // This should throw an error because mkdir -p will fail
142
- await expect(dbModule.initDb()).rejects.toThrow();
143
- });
144
- });
145
-
146
- describe("closeDb", () => {
147
- test("closes database connection", async () => {
148
- process.env.XDG_DATA_HOME = tempDir;
149
-
150
- await dbModule.initDb();
151
- dbModule.closeDb();
152
-
153
- // After closing, getDb() should throw an error
154
- expect(() => dbModule.getDb()).toThrow("Database not initialized");
155
- });
156
-
157
- test("resets db and sqliteDb to null", async () => {
158
- process.env.XDG_DATA_HOME = tempDir;
159
-
160
- await dbModule.initDb();
161
- dbModule.closeDb();
162
-
163
- // Initialize again to verify it works after closing
164
- const db = await dbModule.initDb();
165
- expect(db).toBeDefined();
166
- });
167
-
168
- test("handles already closed database gracefully", async () => {
169
- process.env.XDG_DATA_HOME = tempDir;
170
-
171
- await dbModule.initDb();
172
- dbModule.closeDb();
173
-
174
- // Calling closeDb again should not throw
175
- expect(() => dbModule.closeDb()).not.toThrow();
176
- });
177
- });
178
-
179
- describe("getDb", () => {
180
- test("throws error when database not initialized", () => {
181
- expect(() => dbModule.getDb()).toThrow("Database not initialized. Call initDb() first.");
182
- });
183
-
184
- test("returns database instance after initDb", async () => {
185
- process.env.XDG_DATA_HOME = tempDir;
186
-
187
- await dbModule.initDb();
188
- const db = dbModule.getDb();
189
-
190
- expect(db).toBeDefined();
191
- expect(typeof db.select).toBe("function");
192
- expect(typeof db.insert).toBe("function");
193
- expect(typeof db.update).toBe("function");
194
- expect(typeof db.delete).toBe("function");
195
- });
196
-
197
- test("returns same instance on multiple calls", async () => {
198
- process.env.XDG_DATA_HOME = tempDir;
199
-
200
- await dbModule.initDb();
201
- const db1 = dbModule.getDb();
202
- const db2 = dbModule.getDb();
203
-
204
- expect(db1).toBe(db2);
205
- });
206
- });
207
-
208
- describe("clearCache", () => {
209
- test("deletes all projects", async () => {
210
- process.env.XDG_DATA_HOME = tempDir;
211
-
212
- const db = await dbModule.initDb();
213
-
214
- // Insert some test data
215
- await db.insert(dbModule.schema.projects).values([
216
- { id: "test-1", name: "Test 1", path: "/test/1", type: "git" },
217
- { id: "test-2", name: "Test 2", path: "/test/2", type: "git" },
218
- ]).run();
219
-
220
- // Verify data exists
221
- let projects = await db.select().from(dbModule.schema.projects).all();
222
- expect(projects).toHaveLength(2);
223
-
224
- // Clear cache
225
- await dbModule.clearCache();
226
-
227
- // Verify projects are deleted
228
- projects = await db.select().from(dbModule.schema.projects).all();
229
- expect(projects).toHaveLength(0);
230
- });
231
-
232
- test("deletes all remoteStatus entries", async () => {
233
- process.env.XDG_DATA_HOME = tempDir;
234
-
235
- const db = await dbModule.initDb();
236
-
237
- // Insert a project first (foreign key constraint)
238
- await db.insert(dbModule.schema.projects).values({
239
- id: "test-1",
240
- name: "Test 1",
241
- path: "/test/1",
242
- type: "git",
243
- }).run();
244
-
245
- // Insert remote status
246
- await db.insert(dbModule.schema.remoteStatus).values({
247
- projectId: "test-1",
248
- unpulledCommits: 5,
249
- }).run();
250
-
251
- // Verify data exists
252
- let remoteStatus = await db.select().from(dbModule.schema.remoteStatus).all();
253
- expect(remoteStatus).toHaveLength(1);
254
-
255
- // Clear cache
256
- await dbModule.clearCache();
257
-
258
- // Verify remote status is deleted
259
- remoteStatus = await db.select().from(dbModule.schema.remoteStatus).all();
260
- expect(remoteStatus).toHaveLength(0);
261
- });
262
-
263
- test("clears cache successfully", async () => {
264
- process.env.XDG_DATA_HOME = tempDir;
265
-
266
- const db = await dbModule.initDb();
267
-
268
- // Insert some test data
269
- await db.insert(dbModule.schema.projects).values({
270
- id: "clear-test",
271
- name: "Clear Test",
272
- path: "/clear/test",
273
- type: "git",
274
- }).run();
275
-
276
- // Verify data exists
277
- let projects = await db.select().from(dbModule.schema.projects).all();
278
- expect(projects).toHaveLength(1);
279
-
280
- // Clear cache
281
- await dbModule.clearCache();
282
-
283
- // Verify projects are deleted
284
- projects = await db.select().from(dbModule.schema.projects).all();
285
- expect(projects).toHaveLength(0);
286
- });
287
- });
288
-
289
- describe("Integration with existing schema tests", () => {
290
- test("works with existing database operations", async () => {
291
- process.env.XDG_DATA_HOME = tempDir;
292
-
293
- const db = await dbModule.initDb();
294
-
295
- // Test that we can use the database with the schema
296
- const project = {
297
- id: "integration-test",
298
- name: "Integration Test",
299
- path: "/integration/test",
300
- type: "git" as const,
301
- };
302
-
303
- await db.insert(dbModule.schema.projects).values(project).run();
304
-
305
- const result = await db.select()
306
- .from(dbModule.schema.projects)
307
- .where(eq(dbModule.schema.projects.id, "integration-test"))
308
- .get();
309
-
310
- expect(result).toBeDefined();
311
- expect(result!.name).toBe("Integration Test");
312
- });
313
- });
314
- });
@@ -1,186 +0,0 @@
1
- import { mkdirSync, rmSync, writeFileSync, realpathSync } from "fs";
2
- import { join } from "path";
3
- import { tmpdir } from "os";
4
- import { $ } from "bun";
5
-
6
- /**
7
- * Create a temporary directory for testing
8
- * Uses system temp directory to avoid being inside gitforest's git repo
9
- * Returns resolved real path to avoid macOS /var -> /private/var issues
10
- */
11
- export function createTempDir(name: string): string {
12
- const tmpDir = join(tmpdir(), "gitforest-test", name);
13
- rmSync(tmpDir, { recursive: true, force: true });
14
- mkdirSync(tmpDir, { recursive: true });
15
- // Return real path to handle macOS symlinks (/var -> /private/var)
16
- return realpathSync(tmpDir);
17
- }
18
-
19
- /**
20
- * Clean up a temporary directory
21
- */
22
- export function cleanupTempDir(path: string): void {
23
- rmSync(path, { recursive: true, force: true });
24
- }
25
-
26
- /**
27
- * Create a mock git repository
28
- */
29
- export async function createMockGitRepo(
30
- basePath: string,
31
- name: string,
32
- options?: {
33
- withRemote?: boolean;
34
- withChanges?: boolean;
35
- withUntracked?: boolean;
36
- withStaged?: boolean;
37
- commitCount?: number;
38
- }
39
- ): Promise<string> {
40
- // Ensure the base path exists first
41
- mkdirSync(basePath, { recursive: true });
42
-
43
- // Use realpath for consistent paths on macOS
44
- const baseReal = realpathSync(basePath);
45
- const repoPath = join(baseReal, name);
46
- mkdirSync(repoPath, { recursive: true });
47
-
48
- // Initialize git repo
49
- await $`git -C ${repoPath} init`.quiet();
50
- await $`git -C ${repoPath} config user.email "test@test.com"`.quiet();
51
- await $`git -C ${repoPath} config user.name "Test User"`.quiet();
52
- // Disable hooks and GPG signing to ensure reliable testing
53
- await $`git -C ${repoPath} config core.hooksPath /dev/null`.quiet();
54
- await $`git -C ${repoPath} config commit.gpgsign false`.quiet();
55
-
56
- // Create initial commit
57
- writeFileSync(join(repoPath, "README.md"), `# ${name}\n`);
58
- await $`git -C ${repoPath} add README.md`.quiet();
59
- await $`git -C ${repoPath} commit -m "Initial commit"`.quiet();
60
-
61
- // Add additional commits if requested
62
- const commitCount = options?.commitCount ?? 0;
63
- for (let i = 0; i < commitCount; i++) {
64
- writeFileSync(join(repoPath, `file${i}.txt`), `Content ${i}\n`);
65
- await $`git -C ${repoPath} add file${i}.txt`.quiet();
66
- await $`git -C ${repoPath} commit -m "Commit ${i + 1}"`.quiet();
67
- }
68
-
69
- // Add unstaged changes
70
- if (options?.withChanges) {
71
- writeFileSync(join(repoPath, "README.md"), `# ${name}\n\nModified\n`);
72
- }
73
-
74
- // Add untracked files
75
- if (options?.withUntracked) {
76
- writeFileSync(join(repoPath, "untracked.txt"), "Untracked content\n");
77
- }
78
-
79
- // Add staged changes
80
- if (options?.withStaged) {
81
- writeFileSync(join(repoPath, "staged.txt"), "Staged content\n");
82
- await $`git -C ${repoPath} add staged.txt`.quiet();
83
- }
84
-
85
- return repoPath;
86
- }
87
-
88
- /**
89
- * Create a mock non-git project with a marker file
90
- */
91
- export function createMockProject(
92
- basePath: string,
93
- name: string,
94
- marker: string
95
- ): string {
96
- // Ensure the base path exists first
97
- mkdirSync(basePath, { recursive: true });
98
-
99
- // Use realpath for consistent paths on macOS
100
- const baseReal = realpathSync(basePath);
101
- const projectPath = join(baseReal, name);
102
- mkdirSync(projectPath, { recursive: true });
103
-
104
- // Create marker file based on type
105
- switch (marker) {
106
- case "package.json":
107
- writeFileSync(
108
- join(projectPath, "package.json"),
109
- JSON.stringify({ name, version: "1.0.0" }, null, 2)
110
- );
111
- break;
112
- case "Cargo.toml":
113
- writeFileSync(
114
- join(projectPath, "Cargo.toml"),
115
- `[package]\nname = "${name}"\nversion = "0.1.0"\n`
116
- );
117
- break;
118
- case "pyproject.toml":
119
- writeFileSync(
120
- join(projectPath, "pyproject.toml"),
121
- `[project]\nname = "${name}"\nversion = "0.1.0"\n`
122
- );
123
- break;
124
- case "go.mod":
125
- writeFileSync(join(projectPath, "go.mod"), `module ${name}\n\ngo 1.21\n`);
126
- break;
127
- default:
128
- writeFileSync(join(projectPath, marker), "");
129
- }
130
-
131
- return projectPath;
132
- }
133
-
134
- /**
135
- * Create a mock git repo with submodule
136
- */
137
- export async function createMockRepoWithSubmodule(
138
- basePath: string,
139
- parentName: string,
140
- submoduleName: string
141
- ): Promise<{ parentPath: string; submodulePath: string }> {
142
- // Ensure the base path exists first
143
- mkdirSync(basePath, { recursive: true });
144
-
145
- // Create the submodule repo first (use realpath to ensure consistent paths)
146
- const submoduleSource = await createMockGitRepo(basePath, `${submoduleName}-source`);
147
- const submoduleSourceReal = realpathSync(submoduleSource);
148
-
149
- // Create parent repo
150
- const parentPath = await createMockGitRepo(basePath, parentName);
151
- const parentPathReal = realpathSync(parentPath);
152
-
153
- // Allow file:// protocol for submodule add (needed for local testing)
154
- // Set in both the parent repo and globally for the submodule clone operation
155
- await $`git -C ${parentPathReal} config --local protocol.file.allow always`.quiet();
156
- await $`git config --global protocol.file.allow always`.quiet();
157
-
158
- // Add submodule using real paths
159
- await $`git -C ${parentPathReal} submodule add ${submoduleSourceReal} ${submoduleName}`.quiet();
160
- await $`git -C ${parentPathReal} commit -m "Add submodule"`.quiet();
161
-
162
- const submodulePath = join(parentPathReal, submoduleName);
163
-
164
- return { parentPath: parentPathReal, submodulePath };
165
- }
166
-
167
- /**
168
- * Create a mock config file
169
- */
170
- export function createMockConfig(
171
- basePath: string,
172
- config: Record<string, unknown>
173
- ): string {
174
- const configPath = join(basePath, "gitforest.config.json");
175
- writeFileSync(configPath, JSON.stringify(config, null, 2));
176
- return configPath;
177
- }
178
-
179
- /**
180
- * Create a mock YAML config file
181
- */
182
- export function createMockYamlConfig(basePath: string, content: string): string {
183
- const configPath = join(basePath, "gitforest.config.yaml");
184
- writeFileSync(configPath, content);
185
- return configPath;
186
- }
@@ -1,205 +0,0 @@
1
- /**
2
- * Tests for untested functions in git/commands.ts
3
- */
4
-
5
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
6
- import {
7
- getTrackingBranch,
8
- countUnpushedCommits,
9
- countUnpulledCommits,
10
- getRemoteLastCommitDate,
11
- addRemote,
12
- fetchRemote,
13
- fetchAll,
14
- pull,
15
- push,
16
- getSubmoduleParent,
17
- hasCommits,
18
- } from "../../../src/git/commands.ts";
19
- import {
20
- createTempDir,
21
- cleanupTempDir,
22
- createMockGitRepo,
23
- createMockRepoWithSubmodule,
24
- } from "../fixtures/setup.ts";
25
- import { mkdir, writeFile } from "fs/promises";
26
- import { join } from "path";
27
- import { $ } from "bun";
28
-
29
- describe("Git Commands - Untested Functions", () => {
30
- let tempDir: string;
31
-
32
- beforeEach(() => {
33
- tempDir = createTempDir("git-commands-untested");
34
- });
35
-
36
- afterEach(() => {
37
- cleanupTempDir(tempDir);
38
- });
39
-
40
- describe("getTrackingBranch", () => {
41
- test("returns null when no tracking branch", async () => {
42
- const repoPath = await createMockGitRepo(tempDir, "no-tracking");
43
-
44
- const result = await getTrackingBranch(repoPath);
45
- expect(result).toBeNull();
46
- });
47
-
48
- test("returns null for non-git directory", async () => {
49
- const nonGitPath = join(tempDir, "non-git");
50
- await mkdir(nonGitPath, { recursive: true });
51
-
52
- const result = await getTrackingBranch(nonGitPath);
53
- expect(result).toBeNull();
54
- });
55
- });
56
-
57
- describe("countUnpushedCommits", () => {
58
- test("returns 0 when no tracking branch", async () => {
59
- const repoPath = await createMockGitRepo(tempDir, "no-tracking");
60
-
61
- const result = await countUnpushedCommits(repoPath);
62
- expect(result).toBe(0);
63
- });
64
- });
65
-
66
- describe("countUnpulledCommits", () => {
67
- test("returns 0 when no unpulled commits", async () => {
68
- const repoPath = await createMockGitRepo(tempDir, "no-unpulled");
69
-
70
- const result = await countUnpulledCommits(repoPath);
71
- expect(result).toBe(0);
72
- });
73
- });
74
-
75
- describe("getRemoteLastCommitDate", () => {
76
- test("returns null for non-existent remote branch", async () => {
77
- const repoPath = await createMockGitRepo(tempDir, "no-remote-branch");
78
-
79
- const result = await getRemoteLastCommitDate(repoPath, "origin/nonexistent");
80
- expect(result).toBeNull();
81
- });
82
- });
83
-
84
- describe("addRemote", () => {
85
- test("adds remote with default name", async () => {
86
- const repoPath = await createMockGitRepo(tempDir, "add-remote");
87
-
88
- const result = await addRemote(repoPath, "https://github.com/test/repo.git");
89
-
90
- expect(result).toBe(true);
91
-
92
- // Verify remote was added
93
- const remoteUrl = await $`git -C ${repoPath} config --get remote.origin.url`.text();
94
- expect(remoteUrl.trim()).toBe("https://github.com/test/repo.git");
95
- });
96
-
97
- test("adds remote with custom name", async () => {
98
- const repoPath = await createMockGitRepo(tempDir, "add-custom-remote");
99
-
100
- const result = await addRemote(repoPath, "https://github.com/test/repo.git", "upstream");
101
-
102
- expect(result).toBe(true);
103
-
104
- // Verify remote was added with custom name
105
- const remoteUrl = await $`git -C ${repoPath} config --get remote.upstream.url`.text();
106
- expect(remoteUrl.trim()).toBe("https://github.com/test/repo.git");
107
- });
108
-
109
- test("returns false when remote already exists", async () => {
110
- const repoPath = await createMockGitRepo(tempDir, "existing-remote");
111
-
112
- // Add remote first
113
- await addRemote(repoPath, "https://github.com/test/repo.git", "origin");
114
-
115
- // Try to add again
116
- const result = await addRemote(repoPath, "https://github.com/test/repo2.git", "origin");
117
-
118
- expect(result).toBe(false);
119
- });
120
- });
121
-
122
- describe("fetchRemote", () => {
123
- test("returns false when remote doesn't exist", async () => {
124
- const repoPath = await createMockGitRepo(tempDir, "no-remote-fetch");
125
-
126
- const result = await fetchRemote(repoPath, "nonexistent");
127
- expect(result).toBe(false);
128
- });
129
- });
130
-
131
- describe("fetchAll", () => {
132
- test("handles repos with no remotes", async () => {
133
- const repoPath = await createMockGitRepo(tempDir, "no-remotes");
134
-
135
- const result = await fetchAll(repoPath);
136
- expect(result).toBe(true);
137
- });
138
- });
139
-
140
- describe("pull", () => {
141
- test("returns false on merge conflicts", async () => {
142
- const repoPath = await createMockGitRepo(tempDir, "conflict-repo");
143
-
144
- // Create a conflicting change
145
- await writeFile(join(repoPath, "README.md"), "# Modified");
146
- await $`git -C ${repoPath} add README.md`.quiet();
147
- await $`git -C ${repoPath} commit -m "Local change"`.quiet();
148
-
149
- // Try to pull (will fail due to no remote)
150
- const result = await pull(repoPath);
151
- expect(result).toBe(false);
152
- });
153
- });
154
-
155
- describe("push", () => {
156
- test("returns false when no remote", async () => {
157
- const repoPath = await createMockGitRepo(tempDir, "push-no-remote");
158
-
159
- const result = await push(repoPath, false);
160
- expect(result).toBe(false);
161
- });
162
- });
163
-
164
- describe("getSubmoduleParent", () => {
165
- test("returns parent path for submodule", async () => {
166
- const { submodulePath } = await createMockRepoWithSubmodule(
167
- tempDir,
168
- "parent",
169
- "submodule"
170
- );
171
-
172
- const result = await getSubmoduleParent(submodulePath);
173
- expect(result).toBeTruthy();
174
- expect(result).toContain("parent");
175
- });
176
-
177
- test("returns null for regular repo", async () => {
178
- const repoPath = await createMockGitRepo(tempDir, "regular-repo");
179
-
180
- const result = await getSubmoduleParent(repoPath);
181
- expect(result).toBeNull();
182
- });
183
- });
184
-
185
- describe("hasCommits", () => {
186
- test("returns true for repo with commits", async () => {
187
- const repoPath = await createMockGitRepo(tempDir, "with-commits");
188
-
189
- const result = await hasCommits(repoPath);
190
- expect(result).toBe(true);
191
- });
192
-
193
- test("returns false for empty repo", async () => {
194
- // Create an empty repo without initial commit
195
- const emptyRepoPath = join(tempDir, "empty-repo");
196
- await mkdir(emptyRepoPath, { recursive: true });
197
- await $`git -C ${emptyRepoPath} init`.quiet();
198
- await $`git -C ${emptyRepoPath} config user.email "test@test.com"`.quiet();
199
- await $`git -C ${emptyRepoPath} config user.name "Test"`.quiet();
200
-
201
- const result = await hasCommits(emptyRepoPath);
202
- expect(result).toBe(false);
203
- });
204
- });
205
- });