gitforest 0.1.0 → 1.0.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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +24 -4
  3. package/src/github/auth.ts +3 -3
  4. package/src/utils/debug.ts +4 -4
  5. package/.bunignore +0 -7
  6. package/.github/workflows/ci.yml +0 -73
  7. package/CLAUDE.md +0 -111
  8. package/CONTRIBUTING.md +0 -145
  9. package/bun.lock +0 -267
  10. package/bunfig.toml +0 -15
  11. package/cli +0 -0
  12. package/docs/ai/IMPROVEMENT_PLAN.md +0 -341
  13. package/docs/ai/VERIFICATION_REPORT.md +0 -87
  14. package/docs/ai/architecture.md +0 -169
  15. package/docs/ai/checks/check-2025-12-02-tests.md +0 -40
  16. package/docs/ai/checks/check-2025-12-02.md +0 -55
  17. package/docs/ai/checks/test-verification-report.md +0 -85
  18. package/docs/ai/implementation-guide.md +0 -776
  19. package/docs/ai/research/gitty-codebase-analysis.md +0 -221
  20. package/docs/ai/tickets/GENERAL-sitrep.md +0 -30
  21. package/docs/ai/tickets/TASK-database-tests-sitrep.md +0 -25
  22. package/docs/ai/tickets/TASK-deprecated-functions-sitrep.md +0 -28
  23. package/docs/ai/tickets/TASK-detail-modal-sitrep.md +0 -28
  24. package/docs/ai/tickets/TASK-filter-overlay-sitrep.md +0 -24
  25. package/docs/ai/tickets/TASK-github-service-sitrep.md +0 -32
  26. package/docs/ai/tickets/TASK-github-token-sitrep.md +0 -51
  27. package/docs/ai/tickets/TASK-hascommits-sitrep.md +0 -35
  28. package/docs/ai/tickets/TASK-keybindings-sitrep.md +0 -26
  29. package/docs/ai/tickets/TASK-layout-sitrep.md +0 -25
  30. package/docs/ai/tickets/TASK-markdown-sitrep.md +0 -28
  31. package/docs/ai/tickets/TASK-project-item-sitrep.md +0 -79
  32. package/docs/ai/tickets/TASK-sitrep.md +0 -28
  33. package/docs/ai/tickets/TASK-state-sitrep.md +0 -26
  34. package/docs/ai/tickets/TASK-types-sitrep.md +0 -25
  35. package/docs/ai/tickets/TASK-unified-item-fix-sitrep.md +0 -26
  36. package/docs/ai/tickets/TKT-001-sitrep.md +0 -24
  37. package/docs/ai/tickets/TKT-002-sitrep.md +0 -25
  38. package/docs/ai/tickets/TKT-003-git-service-refactoring-complete.md +0 -46
  39. package/docs/ai/tickets/TKT-003-git-service-refactoring-plan.md +0 -135
  40. package/docs/ai/tickets/TKT-003-sitrep.md +0 -26
  41. package/docs/ai/tickets/TKT-004-sitrep.md +0 -27
  42. package/docs/ai/tickets/TKT-005-sitrep.md +0 -25
  43. package/docs/ai/tickets/TKT-006-sitrep.md +0 -26
  44. package/docs/ai/tickets/TKT-007-sitrep.md +0 -30
  45. package/docs/ai/tickets/TKT-008-sitrep.md +0 -32
  46. package/docs/ai/tickets/TKT-009-sitrep.md +0 -27
  47. package/docs/ai/tickets/TKT-010-sitrep.md +0 -27
  48. package/docs/ai/tickets/TKT-011-sitrep.md +0 -26
  49. package/docs/ai/tickets/TKT-012-sitrep.md +0 -25
  50. package/docs/ai/tickets/sitreps/TASK-actions-sitrep.md +0 -28
  51. package/docs/ai/tickets/sitreps/TASK-actions-test-sitrep.md +0 -25
  52. package/docs/ai/tickets/sitreps/TASK-app-integration-sitrep.md +0 -25
  53. package/docs/ai/tickets/sitreps/TASK-background-fetch-sitrep.md +0 -24
  54. package/docs/ai/tickets/sitreps/TASK-background-fetch-test-sitrep.md +0 -29
  55. package/docs/ai/tickets/sitreps/TASK-batch-tests-sitrep.md +0 -29
  56. package/docs/ai/tickets/sitreps/TASK-bun-test-sitrep.md +0 -26
  57. package/docs/ai/tickets/sitreps/TASK-cache-tests-sitrep.md +0 -30
  58. package/docs/ai/tickets/sitreps/TASK-cli-tests-sitrep.md +0 -28
  59. package/docs/ai/tickets/sitreps/TASK-clone-error-handling-sitrep.md +0 -26
  60. package/docs/ai/tickets/sitreps/TASK-commands-tests-sitrep.md +0 -25
  61. package/docs/ai/tickets/sitreps/TASK-component-tests-1-sitrep.md +0 -30
  62. package/docs/ai/tickets/sitreps/TASK-configloader-tests-sitrep.md +0 -25
  63. package/docs/ai/tickets/sitreps/TASK-confirm-dialog-test-sitrep.md +0 -29
  64. package/docs/ai/tickets/sitreps/TASK-coverage-sitrep.md +0 -95
  65. package/docs/ai/tickets/sitreps/TASK-database-tests-summary.md +0 -61
  66. package/docs/ai/tickets/sitreps/TASK-error-boundary-sitrep.md +0 -30
  67. package/docs/ai/tickets/sitreps/TASK-error-tests-sitrep.md +0 -27
  68. package/docs/ai/tickets/sitreps/TASK-errors-tests-sitrep.md +0 -25
  69. package/docs/ai/tickets/sitreps/TASK-extract-reducer-sitrep.md +0 -27
  70. package/docs/ai/tickets/sitreps/TASK-filter-overlay-test-sitrep.md +0 -25
  71. package/docs/ai/tickets/sitreps/TASK-final-verification-sitrep.md +0 -28
  72. package/docs/ai/tickets/sitreps/TASK-fix-all-tests-sitrep.md +0 -25
  73. package/docs/ai/tickets/sitreps/TASK-fix-hooks-sitrep.md +0 -26
  74. package/docs/ai/tickets/sitreps/TASK-fix-remaining-tests-sitrep.md +0 -25
  75. package/docs/ai/tickets/sitreps/TASK-fix-test-failures-sitrep.md +0 -26
  76. package/docs/ai/tickets/sitreps/TASK-fix-tests-sitrep.md +0 -24
  77. package/docs/ai/tickets/sitreps/TASK-formatters-tests-sitrep.md +0 -25
  78. package/docs/ai/tickets/sitreps/TASK-git-timeouts-sitrep.md +0 -29
  79. package/docs/ai/tickets/sitreps/TASK-github-cache-test-sitrep.md +0 -25
  80. package/docs/ai/tickets/sitreps/TASK-githubcli-tests-sitrep.md +0 -24
  81. package/docs/ai/tickets/sitreps/TASK-gitstatus-tests-sitrep.md +0 -24
  82. package/docs/ai/tickets/sitreps/TASK-hooks-isolation-sitrep.md +0 -27
  83. package/docs/ai/tickets/sitreps/TASK-keybindings-tests-sitrep.md +0 -25
  84. package/docs/ai/tickets/sitreps/TASK-layout-tests-sitrep.md +0 -25
  85. package/docs/ai/tickets/sitreps/TASK-mock-factories-sitrep.md +0 -27
  86. package/docs/ai/tickets/sitreps/TASK-modal-tests-sitrep.md +0 -32
  87. package/docs/ai/tickets/sitreps/TASK-processbatch-fix-sitrep.md +0 -27
  88. package/docs/ai/tickets/sitreps/TASK-projectlist-tests-sitrep.md +0 -30
  89. package/docs/ai/tickets/sitreps/TASK-projectutils-tests-sitrep.md +0 -25
  90. package/docs/ai/tickets/sitreps/TASK-scanner-tests-sitrep.md +0 -29
  91. package/docs/ai/tickets/sitreps/TASK-select-all-sitrep.md +0 -25
  92. package/docs/ai/tickets/sitreps/TASK-shell-error-handling-sitrep.md +0 -27
  93. package/docs/ai/tickets/sitreps/TASK-store-tests-sitrep.md +0 -25
  94. package/docs/ai/tickets/sitreps/TASK-test-fixes-sitrep.md +0 -26
  95. package/docs/ai/tickets/sitreps/TASK-test-summary-sitrep.md +0 -25
  96. package/docs/ai/tickets/sitreps/TASK-test-verification-sitrep.md +0 -27
  97. package/docs/ai/tickets/sitreps/TASK-testsuite-sitrep.md +0 -75
  98. package/docs/ai/tickets/sitreps/TASK-unified-reducer-tests-sitrep.md +0 -29
  99. package/docs/ai/tickets/sitreps/TASK-unified-repos-test-sitrep.md +0 -29
  100. package/docs/ai/tickets/sitreps/TASK-unified-tests-sitrep.md +0 -25
  101. package/docs/ai/tickets/sitreps/TASK-useprojects-tests-sitrep.md +0 -25
  102. package/docs/ai/tickets/sitreps/TASK-utility-tests-sitrep.md +0 -32
  103. package/docs/ai/tickets/sitreps/TKT-003-git-service-refactoring-sitrep.md +0 -64
  104. package/docs/ai/tkt-001-fix-database-error.md +0 -217
  105. package/docs/ai/ui-enhancement-plan.md +0 -562
  106. package/test/integration/app.isolated.tsx +0 -240
  107. package/test/integration/cli-commands.test.ts +0 -287
  108. package/test/integration/cli-validation.test.ts +0 -264
  109. package/test/integration/git-operations.test.ts +0 -218
  110. package/test/integration/scanner.test.ts +0 -228
  111. package/test/preload.ts +0 -18
  112. package/test/unit/cli/commands.test.ts +0 -13
  113. package/test/unit/cli/formatters.test.ts +0 -1116
  114. package/test/unit/cli/github-commands.test.ts +0 -12
  115. package/test/unit/components/CloneDialog.test.tsx +0 -240
  116. package/test/unit/components/ColumnHeader.test.tsx +0 -128
  117. package/test/unit/components/CommandPalette.test.tsx +0 -355
  118. package/test/unit/components/ConfirmDialog.test.tsx +0 -111
  119. package/test/unit/components/ErrorBoundary.test.tsx +0 -139
  120. package/test/unit/components/FilterBar.test.tsx +0 -43
  121. package/test/unit/components/FilterOptionsOverlay.test.tsx +0 -197
  122. package/test/unit/components/HelpOverlay.test.tsx +0 -90
  123. package/test/unit/components/Layout.test.tsx +0 -328
  124. package/test/unit/components/MarkdownRenderer.test.tsx +0 -45
  125. package/test/unit/components/ProgressBar.test.tsx +0 -138
  126. package/test/unit/components/ProjectItem.test.tsx +0 -182
  127. package/test/unit/components/ProjectList.test.tsx +0 -311
  128. package/test/unit/components/RepoDetailModal.test.tsx +0 -445
  129. package/test/unit/components/StatusBar.test.tsx +0 -112
  130. package/test/unit/components/UnifiedProjectItem.test.tsx +0 -618
  131. package/test/unit/components/ViewModeIndicator.test.tsx +0 -137
  132. package/test/unit/components/test-utils.tsx +0 -63
  133. package/test/unit/config/loader.test.ts +0 -692
  134. package/test/unit/db/database.test.ts +0 -978
  135. package/test/unit/db/index.test.ts +0 -314
  136. package/test/unit/fixtures/setup.ts +0 -186
  137. package/test/unit/git/commands-untested.test.ts +0 -205
  138. package/test/unit/git/commands.test.ts +0 -269
  139. package/test/unit/git/operations.test.ts +0 -322
  140. package/test/unit/git/status.test.ts +0 -219
  141. package/test/unit/github/auth.test.ts +0 -317
  142. package/test/unit/github/cache.test.ts +0 -1028
  143. package/test/unit/github/cli.test.ts +0 -135
  144. package/test/unit/github/unified.test.ts +0 -1201
  145. package/test/unit/graceful-shutdown.test.ts +0 -83
  146. package/test/unit/hooks/useBackgroundFetch.test.tsx +0 -239
  147. package/test/unit/hooks/useConfirmDialogActions.test.tsx +0 -81
  148. package/test/unit/hooks/useKeyBindings.isolated.ts +0 -715
  149. package/test/unit/hooks/useProjects.test.tsx +0 -186
  150. package/test/unit/hooks/useUnifiedRepos-simple.test.tsx +0 -115
  151. package/test/unit/hooks/useUnifiedRepos.test.tsx +0 -177
  152. package/test/unit/mocks/config.ts +0 -109
  153. package/test/unit/mocks/git-service.ts +0 -274
  154. package/test/unit/mocks/github-service.ts +0 -250
  155. package/test/unit/mocks/index.ts +0 -72
  156. package/test/unit/mocks/project.ts +0 -148
  157. package/test/unit/mocks/state-mocks.ts +0 -187
  158. package/test/unit/mocks/unified.ts +0 -169
  159. package/test/unit/operations/batch.test.ts +0 -216
  160. package/test/unit/operations/commands.test.ts +0 -550
  161. package/test/unit/scanner/errors.test.ts +0 -297
  162. package/test/unit/scanner/index.test.ts +0 -1011
  163. package/test/unit/scanner/markers.test.ts +0 -150
  164. package/test/unit/scanner/submodules.test.ts +0 -99
  165. package/test/unit/services/git-errors.test.ts +0 -190
  166. package/test/unit/services/git.test.ts +0 -442
  167. package/test/unit/services/github-errors.test.ts +0 -293
  168. package/test/unit/services/github.test.ts +0 -200
  169. package/test/unit/state/actions.test.ts +0 -217
  170. package/test/unit/state/reducer.test.ts +0 -745
  171. package/test/unit/state/store.test.tsx +0 -711
  172. package/test/unit/types/commands.test.ts +0 -220
  173. package/test/unit/types/schema.test.ts +0 -179
  174. package/test/unit/utils/array.test.ts +0 -73
  175. package/test/unit/utils/debug.test.ts +0 -23
  176. package/test/unit/utils/errors.test.ts +0 -295
  177. package/test/unit/utils/markdown.test.ts +0 -163
  178. package/test/unit/utils/project-utils.test.ts +0 -756
  179. package/test/unit/utils/rate-limiter.test.ts +0 -256
  180. package/test/unit/utils/retry.test.ts +0 -165
  181. package/test/unit/utils/strip-ansi.ts +0 -13
  182. package/test/unit/utils/timeout.test.ts +0 -93
  183. 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
- });