pushwork 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.
- package/README.md +460 -0
- package/dist/browser/browser-sync-engine.d.ts +64 -0
- package/dist/browser/browser-sync-engine.d.ts.map +1 -0
- package/dist/browser/browser-sync-engine.js +303 -0
- package/dist/browser/browser-sync-engine.js.map +1 -0
- package/dist/browser/filesystem-adapter.d.ts +84 -0
- package/dist/browser/filesystem-adapter.d.ts.map +1 -0
- package/dist/browser/filesystem-adapter.js +413 -0
- package/dist/browser/filesystem-adapter.js.map +1 -0
- package/dist/browser/index.d.ts +36 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +90 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/types.d.ts +70 -0
- package/dist/browser/types.d.ts.map +1 -0
- package/dist/browser/types.js +6 -0
- package/dist/browser/types.js.map +1 -0
- package/dist/cli/commands.d.ts +71 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +794 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +19 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +199 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/index.d.ts +71 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +314 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/change-detection.d.ts +78 -0
- package/dist/core/change-detection.d.ts.map +1 -0
- package/dist/core/change-detection.js +370 -0
- package/dist/core/change-detection.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +22 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/isomorphic-snapshot.d.ts +58 -0
- package/dist/core/isomorphic-snapshot.d.ts.map +1 -0
- package/dist/core/isomorphic-snapshot.js +204 -0
- package/dist/core/isomorphic-snapshot.js.map +1 -0
- package/dist/core/move-detection.d.ts +72 -0
- package/dist/core/move-detection.d.ts.map +1 -0
- package/dist/core/move-detection.js +200 -0
- package/dist/core/move-detection.js.map +1 -0
- package/dist/core/snapshot.d.ts +109 -0
- package/dist/core/snapshot.d.ts.map +1 -0
- package/dist/core/snapshot.js +263 -0
- package/dist/core/snapshot.js.map +1 -0
- package/dist/core/sync-engine.d.ts +110 -0
- package/dist/core/sync-engine.d.ts.map +1 -0
- package/dist/core/sync-engine.js +817 -0
- package/dist/core/sync-engine.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/platform/browser-filesystem.d.ts +26 -0
- package/dist/platform/browser-filesystem.d.ts.map +1 -0
- package/dist/platform/browser-filesystem.js +91 -0
- package/dist/platform/browser-filesystem.js.map +1 -0
- package/dist/platform/filesystem.d.ts +29 -0
- package/dist/platform/filesystem.d.ts.map +1 -0
- package/dist/platform/filesystem.js +65 -0
- package/dist/platform/filesystem.js.map +1 -0
- package/dist/platform/node-filesystem.d.ts +21 -0
- package/dist/platform/node-filesystem.d.ts.map +1 -0
- package/dist/platform/node-filesystem.js +93 -0
- package/dist/platform/node-filesystem.js.map +1 -0
- package/dist/types/config.d.ts +119 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +3 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/documents.d.ts +70 -0
- package/dist/types/documents.d.ts.map +1 -0
- package/dist/types/documents.js +23 -0
- package/dist/types/documents.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/snapshot.d.ts +81 -0
- package/dist/types/snapshot.d.ts.map +1 -0
- package/dist/types/snapshot.js +17 -0
- package/dist/types/snapshot.js.map +1 -0
- package/dist/utils/content-similarity.d.ts +53 -0
- package/dist/utils/content-similarity.d.ts.map +1 -0
- package/dist/utils/content-similarity.js +155 -0
- package/dist/utils/content-similarity.js.map +1 -0
- package/dist/utils/content.d.ts +5 -0
- package/dist/utils/content.d.ts.map +1 -0
- package/dist/utils/content.js +30 -0
- package/dist/utils/content.js.map +1 -0
- package/dist/utils/fs-browser.d.ts +57 -0
- package/dist/utils/fs-browser.d.ts.map +1 -0
- package/dist/utils/fs-browser.js +311 -0
- package/dist/utils/fs-browser.js.map +1 -0
- package/dist/utils/fs-node.d.ts +53 -0
- package/dist/utils/fs-node.d.ts.map +1 -0
- package/dist/utils/fs-node.js +220 -0
- package/dist/utils/fs-node.js.map +1 -0
- package/dist/utils/fs.d.ts +62 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +293 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +23 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/isomorphic.d.ts +29 -0
- package/dist/utils/isomorphic.d.ts.map +1 -0
- package/dist/utils/isomorphic.js +139 -0
- package/dist/utils/isomorphic.js.map +1 -0
- package/dist/utils/mime-types.d.ts +13 -0
- package/dist/utils/mime-types.d.ts.map +1 -0
- package/dist/utils/mime-types.js +240 -0
- package/dist/utils/mime-types.js.map +1 -0
- package/dist/utils/network-sync.d.ts +12 -0
- package/dist/utils/network-sync.d.ts.map +1 -0
- package/dist/utils/network-sync.js +149 -0
- package/dist/utils/network-sync.js.map +1 -0
- package/dist/utils/pure.d.ts +25 -0
- package/dist/utils/pure.d.ts.map +1 -0
- package/dist/utils/pure.js +112 -0
- package/dist/utils/pure.js.map +1 -0
- package/dist/utils/repo-factory.d.ts +11 -0
- package/dist/utils/repo-factory.d.ts.map +1 -0
- package/dist/utils/repo-factory.js +77 -0
- package/dist/utils/repo-factory.js.map +1 -0
- package/package.json +83 -0
- package/src/cli/commands.ts +1053 -0
- package/src/cli/index.ts +2 -0
- package/src/cli.ts +287 -0
- package/src/config/index.ts +334 -0
- package/src/core/change-detection.ts +484 -0
- package/src/core/index.ts +5 -0
- package/src/core/move-detection.ts +269 -0
- package/src/core/snapshot.ts +285 -0
- package/src/core/sync-engine.ts +1167 -0
- package/src/index.ts +14 -0
- package/src/types/config.ts +130 -0
- package/src/types/documents.ts +72 -0
- package/src/types/index.ts +8 -0
- package/src/types/snapshot.ts +88 -0
- package/src/utils/content-similarity.ts +194 -0
- package/src/utils/content.ts +28 -0
- package/src/utils/fs.ts +289 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/mime-types.ts +236 -0
- package/src/utils/network-sync.ts +153 -0
- package/src/utils/repo-factory.ts +58 -0
- package/test/README-TESTING-GAPS.md +174 -0
- package/test/integration/README.md +328 -0
- package/test/integration/clone-test.sh +310 -0
- package/test/integration/conflict-resolution-test.sh +309 -0
- package/test/integration/deletion-behavior-test.sh +487 -0
- package/test/integration/deletion-sync-test-simple.sh +193 -0
- package/test/integration/deletion-sync-test.sh +297 -0
- package/test/integration/exclude-patterns.test.ts +152 -0
- package/test/integration/full-integration-test.sh +363 -0
- package/test/integration/sync-deletion.test.ts +339 -0
- package/test/integration/sync-flow.test.ts +309 -0
- package/test/run-tests.sh +225 -0
- package/test/unit/content-similarity.test.ts +236 -0
- package/test/unit/deletion-behavior.test.ts +260 -0
- package/test/unit/enhanced-mime-detection.test.ts +266 -0
- package/test/unit/snapshot.test.ts +431 -0
- package/test/unit/sync-timing.test.ts +178 -0
- package/test/unit/utils.test.ts +368 -0
- package/tools/browser-sync/README.md +116 -0
- package/tools/browser-sync/package.json +44 -0
- package/tools/browser-sync/patchwork.json +1 -0
- package/tools/browser-sync/pnpm-lock.yaml +4202 -0
- package/tools/browser-sync/src/components/BrowserSyncTool.tsx +599 -0
- package/tools/browser-sync/src/index.ts +20 -0
- package/tools/browser-sync/src/polyfills.ts +31 -0
- package/tools/browser-sync/src/styles.css +290 -0
- package/tools/browser-sync/src/types.ts +27 -0
- package/tools/browser-sync/vite.config.ts +25 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Comprehensive Integration Test for Pushwork
|
|
4
|
+
# Tests all major functionality of the sync tool
|
|
5
|
+
|
|
6
|
+
set -e # Exit on any error
|
|
7
|
+
|
|
8
|
+
# Colors for output
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[1;33m'
|
|
12
|
+
BLUE='\033[0;34m'
|
|
13
|
+
NC='\033[0m' # No Color
|
|
14
|
+
|
|
15
|
+
# Test configuration
|
|
16
|
+
TEST_DIR="/tmp/pushwork-integration-test"
|
|
17
|
+
PUSHWORK_CMD="node $(pwd)/dist/cli.js"
|
|
18
|
+
CUSTOM_SYNC_SERVER="ws://localhost:3030"
|
|
19
|
+
CUSTOM_STORAGE_ID="1d89eba7-f7a4-4e8e-80f2-5f4e2406f507"
|
|
20
|
+
|
|
21
|
+
# Test counters
|
|
22
|
+
TESTS_RUN=0
|
|
23
|
+
TESTS_PASSED=0
|
|
24
|
+
TESTS_FAILED=0
|
|
25
|
+
|
|
26
|
+
# Helper functions
|
|
27
|
+
log_info() {
|
|
28
|
+
echo -e "${BLUE}[INFO]${NC} $1"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
log_success() {
|
|
32
|
+
echo -e "${GREEN}[PASS]${NC} $1"
|
|
33
|
+
((TESTS_PASSED++))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
log_error() {
|
|
37
|
+
echo -e "${RED}[FAIL]${NC} $1"
|
|
38
|
+
((TESTS_FAILED++))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
log_warning() {
|
|
42
|
+
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
log_test() {
|
|
46
|
+
echo -e "${YELLOW}[TEST]${NC} $1"
|
|
47
|
+
((TESTS_RUN++))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Test wrapper function
|
|
51
|
+
run_test() {
|
|
52
|
+
local test_name="$1"
|
|
53
|
+
local test_cmd="$2"
|
|
54
|
+
local expected_exit_code="${3:-0}"
|
|
55
|
+
|
|
56
|
+
log_test "$test_name"
|
|
57
|
+
|
|
58
|
+
if [ "$expected_exit_code" = "0" ]; then
|
|
59
|
+
if eval "$test_cmd" > /dev/null 2>&1; then
|
|
60
|
+
log_success "$test_name"
|
|
61
|
+
return 0
|
|
62
|
+
else
|
|
63
|
+
log_error "$test_name (command failed)"
|
|
64
|
+
return 1
|
|
65
|
+
fi
|
|
66
|
+
else
|
|
67
|
+
if eval "$test_cmd" > /dev/null 2>&1; then
|
|
68
|
+
log_error "$test_name (expected failure but succeeded)"
|
|
69
|
+
return 1
|
|
70
|
+
else
|
|
71
|
+
log_success "$test_name (correctly failed)"
|
|
72
|
+
return 0
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Cleanup function
|
|
78
|
+
cleanup() {
|
|
79
|
+
log_info "Cleaning up test directory..."
|
|
80
|
+
rm -rf "$TEST_DIR"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Setup function
|
|
84
|
+
setup() {
|
|
85
|
+
log_info "Setting up integration test environment..."
|
|
86
|
+
|
|
87
|
+
# Build the project first
|
|
88
|
+
log_info "Building pushwork..."
|
|
89
|
+
npm run build
|
|
90
|
+
|
|
91
|
+
# Clean up any existing test directory
|
|
92
|
+
rm -rf "$TEST_DIR"
|
|
93
|
+
mkdir -p "$TEST_DIR"
|
|
94
|
+
cd "$TEST_DIR"
|
|
95
|
+
|
|
96
|
+
log_info "Test directory: $TEST_DIR"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Test functions
|
|
100
|
+
|
|
101
|
+
test_help_commands() {
|
|
102
|
+
log_info "=== Testing Help Commands ==="
|
|
103
|
+
|
|
104
|
+
run_test "pushwork --help" "$PUSHWORK_CMD --help"
|
|
105
|
+
run_test "pushwork init --help" "$PUSHWORK_CMD init --help"
|
|
106
|
+
run_test "pushwork clone --help" "$PUSHWORK_CMD clone --help"
|
|
107
|
+
run_test "pushwork sync --help" "$PUSHWORK_CMD sync --help"
|
|
108
|
+
run_test "pushwork status --help" "$PUSHWORK_CMD status --help"
|
|
109
|
+
run_test "pushwork diff --help" "$PUSHWORK_CMD diff --help"
|
|
110
|
+
run_test "pushwork commit --help" "$PUSHWORK_CMD commit --help"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
test_init_functionality() {
|
|
114
|
+
log_info "=== Testing Init Functionality ==="
|
|
115
|
+
|
|
116
|
+
# Test init with default settings
|
|
117
|
+
mkdir -p test-init-default
|
|
118
|
+
cd test-init-default
|
|
119
|
+
echo "Hello World" > test.txt
|
|
120
|
+
|
|
121
|
+
run_test "init with default settings" "$PUSHWORK_CMD init ."
|
|
122
|
+
run_test "check .pushwork directory exists" "[ -d .pushwork ]"
|
|
123
|
+
run_test "check config file exists" "[ -f .pushwork/config.json ]"
|
|
124
|
+
run_test "check snapshot file exists" "[ -f .pushwork/snapshot.json ]"
|
|
125
|
+
|
|
126
|
+
cd ..
|
|
127
|
+
|
|
128
|
+
# Test init with custom sync server
|
|
129
|
+
mkdir -p test-init-custom
|
|
130
|
+
cd test-init-custom
|
|
131
|
+
echo "Custom Server Test" > custom.txt
|
|
132
|
+
|
|
133
|
+
run_test "init with custom sync server" "$PUSHWORK_CMD init . --sync-server $CUSTOM_SYNC_SERVER --sync-server-storage-id $CUSTOM_STORAGE_ID"
|
|
134
|
+
|
|
135
|
+
# Verify custom settings in config
|
|
136
|
+
if [ -f .pushwork/config.json ]; then
|
|
137
|
+
if grep -q "$CUSTOM_SYNC_SERVER" .pushwork/config.json; then
|
|
138
|
+
log_success "custom sync server saved in config"
|
|
139
|
+
((TESTS_PASSED++))
|
|
140
|
+
else
|
|
141
|
+
log_error "custom sync server not found in config"
|
|
142
|
+
((TESTS_FAILED++))
|
|
143
|
+
fi
|
|
144
|
+
((TESTS_RUN++))
|
|
145
|
+
|
|
146
|
+
if grep -q "$CUSTOM_STORAGE_ID" .pushwork/config.json; then
|
|
147
|
+
log_success "custom storage ID saved in config"
|
|
148
|
+
((TESTS_PASSED++))
|
|
149
|
+
else
|
|
150
|
+
log_error "custom storage ID not found in config"
|
|
151
|
+
((TESTS_FAILED++))
|
|
152
|
+
fi
|
|
153
|
+
((TESTS_RUN++))
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
cd ..
|
|
157
|
+
|
|
158
|
+
# Test error cases
|
|
159
|
+
run_test "init already initialized directory" "$PUSHWORK_CMD init test-init-default" 1
|
|
160
|
+
run_test "init with only sync-server (should fail)" "$PUSHWORK_CMD init test-fail --sync-server $CUSTOM_SYNC_SERVER" 1
|
|
161
|
+
run_test "init with only storage-id (should fail)" "$PUSHWORK_CMD init test-fail --sync-server-storage-id $CUSTOM_STORAGE_ID" 1
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
test_status_functionality() {
|
|
165
|
+
log_info "=== Testing Status Functionality ==="
|
|
166
|
+
|
|
167
|
+
cd test-init-default
|
|
168
|
+
run_test "status in initialized directory" "$PUSHWORK_CMD status"
|
|
169
|
+
cd ..
|
|
170
|
+
|
|
171
|
+
run_test "status in non-initialized directory" "$PUSHWORK_CMD status" 1
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
test_commit_functionality() {
|
|
175
|
+
log_info "=== Testing Commit Functionality ==="
|
|
176
|
+
|
|
177
|
+
cd test-init-default
|
|
178
|
+
|
|
179
|
+
# Add some files
|
|
180
|
+
echo "New content" > new-file.txt
|
|
181
|
+
echo "Modified content" >> test.txt
|
|
182
|
+
|
|
183
|
+
run_test "commit with changes" "$PUSHWORK_CMD commit ."
|
|
184
|
+
run_test "commit dry-run" "$PUSHWORK_CMD commit . --dry-run"
|
|
185
|
+
|
|
186
|
+
cd ..
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
test_sync_functionality() {
|
|
190
|
+
log_info "=== Testing Sync Functionality ==="
|
|
191
|
+
|
|
192
|
+
cd test-init-default
|
|
193
|
+
|
|
194
|
+
run_test "sync in initialized directory" "$PUSHWORK_CMD sync"
|
|
195
|
+
run_test "sync dry-run" "$PUSHWORK_CMD sync --dry-run"
|
|
196
|
+
run_test "sync local-only" "$PUSHWORK_CMD sync --local-only"
|
|
197
|
+
|
|
198
|
+
cd ..
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
test_diff_functionality() {
|
|
202
|
+
log_info "=== Testing Diff Functionality ==="
|
|
203
|
+
|
|
204
|
+
cd test-init-default
|
|
205
|
+
|
|
206
|
+
# Make some changes
|
|
207
|
+
echo "Diff test content" > diff-test.txt
|
|
208
|
+
|
|
209
|
+
run_test "diff command" "$PUSHWORK_CMD diff"
|
|
210
|
+
run_test "diff name-only" "$PUSHWORK_CMD diff --name-only"
|
|
211
|
+
run_test "diff local-only" "$PUSHWORK_CMD diff --local-only"
|
|
212
|
+
|
|
213
|
+
cd ..
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
test_clone_functionality() {
|
|
217
|
+
log_info "=== Testing Clone Functionality ==="
|
|
218
|
+
|
|
219
|
+
# First, we need a valid URL to clone from the initialized directory
|
|
220
|
+
cd test-init-default
|
|
221
|
+
|
|
222
|
+
if [ -f .pushwork/snapshot.json ]; then
|
|
223
|
+
ROOT_URL=$($PUSHWORK_CMD url .)
|
|
224
|
+
|
|
225
|
+
if [ -n "$ROOT_URL" ]; then
|
|
226
|
+
cd ..
|
|
227
|
+
|
|
228
|
+
# Test clone with default settings
|
|
229
|
+
run_test "clone with default settings" "$PUSHWORK_CMD clone $ROOT_URL test-clone-default"
|
|
230
|
+
|
|
231
|
+
if [ -d test-clone-default ]; then
|
|
232
|
+
run_test "cloned directory has .pushwork" "[ -d test-clone-default/.pushwork ]"
|
|
233
|
+
run_test "cloned directory has files" "[ -f test-clone-default/test.txt ]"
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
# Test clone with custom sync server
|
|
237
|
+
run_test "clone with custom sync server" "$PUSHWORK_CMD clone $ROOT_URL test-clone-custom --sync-server $CUSTOM_SYNC_SERVER --sync-server-storage-id $CUSTOM_STORAGE_ID"
|
|
238
|
+
|
|
239
|
+
# Test clone error cases
|
|
240
|
+
mkdir -p existing-dir
|
|
241
|
+
echo "existing" > existing-dir/file.txt
|
|
242
|
+
run_test "clone to non-empty directory (should fail)" "$PUSHWORK_CMD clone $ROOT_URL existing-dir" 1
|
|
243
|
+
run_test "clone with force to non-empty directory" "$PUSHWORK_CMD clone $ROOT_URL existing-dir --force"
|
|
244
|
+
|
|
245
|
+
run_test "clone with only sync-server (should fail)" "$PUSHWORK_CMD clone $ROOT_URL test-fail --sync-server $CUSTOM_SYNC_SERVER" 1
|
|
246
|
+
run_test "clone with only storage-id (should fail)" "$PUSHWORK_CMD clone $ROOT_URL test-fail --sync-server-storage-id $CUSTOM_STORAGE_ID" 1
|
|
247
|
+
else
|
|
248
|
+
log_warning "No valid root URL found, skipping clone tests"
|
|
249
|
+
fi
|
|
250
|
+
else
|
|
251
|
+
log_warning "Snapshot missing - repository not properly initialized, skipping clone tests"
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
cd ..
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
test_bidirectional_sync() {
|
|
258
|
+
log_info "=== Testing Bidirectional Sync ==="
|
|
259
|
+
|
|
260
|
+
# This test requires both directories to be properly initialized
|
|
261
|
+
if [ -d test-init-default ] && [ -d test-clone-default ]; then
|
|
262
|
+
# Add content in original
|
|
263
|
+
cd test-init-default
|
|
264
|
+
echo "From original" > sync-test.txt
|
|
265
|
+
$PUSHWORK_CMD commit . > /dev/null 2>&1 || true
|
|
266
|
+
cd ..
|
|
267
|
+
|
|
268
|
+
# Add different content in clone
|
|
269
|
+
cd test-clone-default
|
|
270
|
+
echo "From clone" > sync-test-clone.txt
|
|
271
|
+
$PUSHWORK_CMD commit . > /dev/null 2>&1 || true
|
|
272
|
+
cd ..
|
|
273
|
+
|
|
274
|
+
# Try to sync both
|
|
275
|
+
cd test-init-default
|
|
276
|
+
run_test "sync from original" "$PUSHWORK_CMD sync --local-only"
|
|
277
|
+
cd ..
|
|
278
|
+
|
|
279
|
+
cd test-clone-default
|
|
280
|
+
run_test "sync from clone" "$PUSHWORK_CMD sync --local-only"
|
|
281
|
+
cd ..
|
|
282
|
+
else
|
|
283
|
+
log_warning "Clone directories not available, skipping bidirectional sync test"
|
|
284
|
+
fi
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
test_file_operations() {
|
|
288
|
+
log_info "=== Testing File Operations ==="
|
|
289
|
+
|
|
290
|
+
mkdir -p test-file-ops
|
|
291
|
+
cd test-file-ops
|
|
292
|
+
|
|
293
|
+
# Initialize
|
|
294
|
+
$PUSHWORK_CMD init . > /dev/null 2>&1
|
|
295
|
+
|
|
296
|
+
# Test various file operations
|
|
297
|
+
echo "Text file" > text.txt
|
|
298
|
+
echo -e "\x89PNG\r\n\x1a\n" > binary.png # Fake PNG header
|
|
299
|
+
mkdir -p subdir
|
|
300
|
+
echo "Subdirectory file" > subdir/nested.txt
|
|
301
|
+
|
|
302
|
+
run_test "commit various file types" "$PUSHWORK_CMD commit ."
|
|
303
|
+
run_test "status after file operations" "$PUSHWORK_CMD status"
|
|
304
|
+
|
|
305
|
+
# Modify files
|
|
306
|
+
echo "Modified text" >> text.txt
|
|
307
|
+
rm binary.png
|
|
308
|
+
echo "New file" > new.txt
|
|
309
|
+
|
|
310
|
+
run_test "diff after modifications" "$PUSHWORK_CMD diff --name-only"
|
|
311
|
+
run_test "commit modifications" "$PUSHWORK_CMD commit ."
|
|
312
|
+
|
|
313
|
+
cd ..
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
# Main test execution
|
|
317
|
+
main() {
|
|
318
|
+
echo "======================================"
|
|
319
|
+
echo "Pushwork Integration Test Suite"
|
|
320
|
+
echo "======================================"
|
|
321
|
+
|
|
322
|
+
# Trap cleanup on exit
|
|
323
|
+
trap cleanup EXIT
|
|
324
|
+
|
|
325
|
+
# Setup
|
|
326
|
+
setup
|
|
327
|
+
|
|
328
|
+
# Run all tests
|
|
329
|
+
test_help_commands
|
|
330
|
+
test_init_functionality
|
|
331
|
+
test_status_functionality
|
|
332
|
+
test_commit_functionality
|
|
333
|
+
test_sync_functionality
|
|
334
|
+
test_diff_functionality
|
|
335
|
+
test_clone_functionality
|
|
336
|
+
test_bidirectional_sync
|
|
337
|
+
test_file_operations
|
|
338
|
+
|
|
339
|
+
# Summary
|
|
340
|
+
echo ""
|
|
341
|
+
echo "======================================"
|
|
342
|
+
echo "Test Results Summary"
|
|
343
|
+
echo "======================================"
|
|
344
|
+
echo "Tests Run: $TESTS_RUN"
|
|
345
|
+
echo "Tests Passed: $TESTS_PASSED"
|
|
346
|
+
echo "Tests Failed: $TESTS_FAILED"
|
|
347
|
+
|
|
348
|
+
if [ $TESTS_FAILED -eq 0 ]; then
|
|
349
|
+
log_success "All tests passed!"
|
|
350
|
+
exit 0
|
|
351
|
+
else
|
|
352
|
+
log_error "Some tests failed!"
|
|
353
|
+
exit 1
|
|
354
|
+
fi
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# Check if jq is available (optional dependency)
|
|
358
|
+
if ! command -v jq &> /dev/null; then
|
|
359
|
+
log_warning "jq is not installed - some tests may be skipped"
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
# Run the tests
|
|
363
|
+
main "$@"
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import {
|
|
5
|
+
readFileContent,
|
|
6
|
+
writeFileContent,
|
|
7
|
+
removePath,
|
|
8
|
+
pathExists,
|
|
9
|
+
} from "../../src/utils";
|
|
10
|
+
import { SnapshotManager } from "../../src/core/snapshot";
|
|
11
|
+
import { ChangeDetector } from "../../src/core/change-detection";
|
|
12
|
+
import { ChangeType } from "../../src/types";
|
|
13
|
+
|
|
14
|
+
describe("Sync Engine Deletion Integration", () => {
|
|
15
|
+
let testDir: string;
|
|
16
|
+
let snapshotManager: SnapshotManager;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
testDir = await fs.mkdtemp(path.join(tmpdir(), "sync-deletion-test-"));
|
|
20
|
+
snapshotManager = new SnapshotManager(testDir);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("Deletion Detection Logic", () => {
|
|
28
|
+
it("should properly detect local file deletions", async () => {
|
|
29
|
+
console.log("\n๐งช Testing Local File Deletion Detection");
|
|
30
|
+
|
|
31
|
+
// Create initial state
|
|
32
|
+
const filePath = path.join(testDir, "will-be-deleted.ts");
|
|
33
|
+
const content = "interface ToDelete { id: number; }";
|
|
34
|
+
await writeFileContent(filePath, content);
|
|
35
|
+
|
|
36
|
+
// Create snapshot representing the "before" state
|
|
37
|
+
const snapshot = snapshotManager.createEmpty();
|
|
38
|
+
snapshotManager.updateFileEntry(snapshot, "will-be-deleted.ts", {
|
|
39
|
+
path: filePath,
|
|
40
|
+
url: "automerge:deletion-test" as any,
|
|
41
|
+
head: ["before-deletion"] as any,
|
|
42
|
+
extension: "ts",
|
|
43
|
+
mimeType: "text/typescript",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
console.log(`๐ Created file: ${filePath}`);
|
|
47
|
+
console.log(`๐ธ Snapshot has ${snapshot.files.size} files`);
|
|
48
|
+
|
|
49
|
+
// Verify initial state
|
|
50
|
+
expect(await pathExists(filePath)).toBe(true);
|
|
51
|
+
expect(snapshot.files.has("will-be-deleted.ts")).toBe(true);
|
|
52
|
+
|
|
53
|
+
// Simulate user deleting the file
|
|
54
|
+
await removePath(filePath);
|
|
55
|
+
console.log(`๐๏ธ File deleted from filesystem`);
|
|
56
|
+
|
|
57
|
+
// File should be gone from filesystem but still in snapshot
|
|
58
|
+
expect(await pathExists(filePath)).toBe(false);
|
|
59
|
+
expect(snapshot.files.has("will-be-deleted.ts")).toBe(true);
|
|
60
|
+
|
|
61
|
+
console.log(`โ
Deletion properly detected`);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should handle multiple file deletions correctly", async () => {
|
|
65
|
+
console.log("\n๐งช Testing Multiple File Deletions");
|
|
66
|
+
|
|
67
|
+
const testFiles = [
|
|
68
|
+
{ name: "delete1.ts", content: "interface One { x: number; }" },
|
|
69
|
+
{ name: "delete2.js", content: "const two = 'value';" },
|
|
70
|
+
{ name: "delete3.json", content: '{"three": true}' },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const snapshot = snapshotManager.createEmpty();
|
|
74
|
+
|
|
75
|
+
// Create all files and add to snapshot
|
|
76
|
+
for (const file of testFiles) {
|
|
77
|
+
const filePath = path.join(testDir, file.name);
|
|
78
|
+
await writeFileContent(filePath, file.content);
|
|
79
|
+
|
|
80
|
+
snapshotManager.updateFileEntry(snapshot, file.name, {
|
|
81
|
+
path: filePath,
|
|
82
|
+
url: `automerge:${file.name}` as any,
|
|
83
|
+
head: [`head-${file.name}`] as any,
|
|
84
|
+
extension: path.extname(file.name).slice(1),
|
|
85
|
+
mimeType: file.name.endsWith(".ts")
|
|
86
|
+
? "text/typescript"
|
|
87
|
+
: "text/plain",
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log(`๐ Created: ${file.name}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
expect(snapshot.files.size).toBe(3);
|
|
94
|
+
|
|
95
|
+
// Delete all files
|
|
96
|
+
for (const file of testFiles) {
|
|
97
|
+
const filePath = path.join(testDir, file.name);
|
|
98
|
+
await removePath(filePath);
|
|
99
|
+
console.log(`๐๏ธ Deleted: ${file.name}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Verify all files are gone from filesystem
|
|
103
|
+
for (const file of testFiles) {
|
|
104
|
+
const filePath = path.join(testDir, file.name);
|
|
105
|
+
expect(await pathExists(filePath)).toBe(false);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Snapshot should still have entries (until sync processes them)
|
|
109
|
+
expect(snapshot.files.size).toBe(3);
|
|
110
|
+
|
|
111
|
+
// Simulate sync engine processing the deletions
|
|
112
|
+
for (const file of testFiles) {
|
|
113
|
+
snapshotManager.removeFileEntry(snapshot, file.name);
|
|
114
|
+
console.log(`๐ธ Removed from snapshot: ${file.name}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
expect(snapshot.files.size).toBe(0);
|
|
118
|
+
console.log(`โ
Multiple deletions handled correctly`);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("Deletion Timing and Race Conditions", () => {
|
|
123
|
+
it("should handle rapid create-modify-delete sequences", async () => {
|
|
124
|
+
console.log("\n๐งช Testing Rapid Create-Modify-Delete Sequences");
|
|
125
|
+
|
|
126
|
+
const filePath = path.join(testDir, "rapid-changes.ts");
|
|
127
|
+
const snapshot = snapshotManager.createEmpty();
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < 3; i++) {
|
|
130
|
+
console.log(`\n--- Cycle ${i + 1} ---`);
|
|
131
|
+
|
|
132
|
+
// Create
|
|
133
|
+
const content = `interface Cycle${i} { value: ${i}; }`;
|
|
134
|
+
await writeFileContent(filePath, content);
|
|
135
|
+
console.log(`๐ Created with content: "${content}"`);
|
|
136
|
+
|
|
137
|
+
// Add to snapshot
|
|
138
|
+
snapshotManager.updateFileEntry(snapshot, "rapid-changes.ts", {
|
|
139
|
+
path: filePath,
|
|
140
|
+
url: `automerge:cycle-${i}` as any,
|
|
141
|
+
head: [`head-${i}`] as any,
|
|
142
|
+
extension: "ts",
|
|
143
|
+
mimeType: "text/typescript",
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Modify
|
|
147
|
+
const modifiedContent = content + `\n// Modified in cycle ${i}`;
|
|
148
|
+
await writeFileContent(filePath, modifiedContent);
|
|
149
|
+
console.log(`๐ Modified content`);
|
|
150
|
+
|
|
151
|
+
// Delete
|
|
152
|
+
await removePath(filePath);
|
|
153
|
+
console.log(`๐๏ธ Deleted`);
|
|
154
|
+
|
|
155
|
+
// Verify deletion
|
|
156
|
+
expect(await pathExists(filePath)).toBe(false);
|
|
157
|
+
|
|
158
|
+
// Clean up snapshot
|
|
159
|
+
snapshotManager.removeFileEntry(snapshot, "rapid-changes.ts");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log(`โ
Rapid sequences handled without errors`);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should handle deletion during content modification attempts", async () => {
|
|
166
|
+
console.log("\n๐งช Testing Deletion During Modification");
|
|
167
|
+
|
|
168
|
+
const filePath = path.join(testDir, "modify-delete-race.ts");
|
|
169
|
+
const initialContent = "interface Race { test: boolean; }";
|
|
170
|
+
|
|
171
|
+
// Create initial file
|
|
172
|
+
await writeFileContent(filePath, initialContent);
|
|
173
|
+
console.log(`๐ Created file with initial content`);
|
|
174
|
+
|
|
175
|
+
// Start modification and deletion concurrently
|
|
176
|
+
const modifyPromise = writeFileContent(
|
|
177
|
+
filePath,
|
|
178
|
+
initialContent + "\n// Modified"
|
|
179
|
+
);
|
|
180
|
+
const deletePromise = (async () => {
|
|
181
|
+
// Small delay to let modification start
|
|
182
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
183
|
+
await removePath(filePath);
|
|
184
|
+
})();
|
|
185
|
+
|
|
186
|
+
// Wait for both operations to complete
|
|
187
|
+
await Promise.allSettled([modifyPromise, deletePromise]);
|
|
188
|
+
|
|
189
|
+
// File should be deleted regardless of modification timing
|
|
190
|
+
expect(await pathExists(filePath)).toBe(false);
|
|
191
|
+
console.log(`โ
File properly deleted despite concurrent modification`);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("Directory Structure Impact", () => {
|
|
196
|
+
it("should handle deletion of files in nested directories", async () => {
|
|
197
|
+
console.log("\n๐งช Testing Nested Directory File Deletion");
|
|
198
|
+
|
|
199
|
+
// Create nested structure
|
|
200
|
+
const nestedDir = path.join(testDir, "src", "components");
|
|
201
|
+
const filePath = path.join(nestedDir, "Button.tsx");
|
|
202
|
+
const content = "export const Button = () => <button>Click</button>;";
|
|
203
|
+
|
|
204
|
+
await fs.mkdir(nestedDir, { recursive: true });
|
|
205
|
+
await writeFileContent(filePath, content);
|
|
206
|
+
|
|
207
|
+
const snapshot = snapshotManager.createEmpty();
|
|
208
|
+
snapshotManager.updateFileEntry(snapshot, "src/components/Button.tsx", {
|
|
209
|
+
path: filePath,
|
|
210
|
+
url: "automerge:nested-button" as any,
|
|
211
|
+
head: ["nested-head"] as any,
|
|
212
|
+
extension: "tsx",
|
|
213
|
+
mimeType: "text/tsx",
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
console.log(`๐ Created nested structure: ${nestedDir}`);
|
|
217
|
+
console.log(`๐ Created file: src/components/Button.tsx`);
|
|
218
|
+
|
|
219
|
+
// Delete just the file (not the directories)
|
|
220
|
+
await removePath(filePath);
|
|
221
|
+
console.log(`๐๏ธ Deleted file, kept directory structure`);
|
|
222
|
+
|
|
223
|
+
// File should be gone, directories should remain
|
|
224
|
+
expect(await pathExists(filePath)).toBe(false);
|
|
225
|
+
expect(await pathExists(nestedDir)).toBe(true);
|
|
226
|
+
expect(await pathExists(path.join(testDir, "src"))).toBe(true);
|
|
227
|
+
|
|
228
|
+
// Simulate snapshot cleanup
|
|
229
|
+
snapshotManager.removeFileEntry(snapshot, "src/components/Button.tsx");
|
|
230
|
+
expect(snapshot.files.size).toBe(0);
|
|
231
|
+
|
|
232
|
+
console.log(`โ
Nested file deletion handled correctly`);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should handle deletion of entire directory trees", async () => {
|
|
236
|
+
console.log("\n๐งช Testing Directory Tree Deletion");
|
|
237
|
+
|
|
238
|
+
// Create multiple files in nested structure
|
|
239
|
+
const testStructure = [
|
|
240
|
+
"src/utils/helpers.ts",
|
|
241
|
+
"src/utils/constants.ts",
|
|
242
|
+
"src/components/Button.tsx",
|
|
243
|
+
"src/components/Input.tsx",
|
|
244
|
+
"src/types/index.ts",
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
const snapshot = snapshotManager.createEmpty();
|
|
248
|
+
|
|
249
|
+
for (const relativePath of testStructure) {
|
|
250
|
+
const fullPath = path.join(testDir, relativePath);
|
|
251
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
252
|
+
await writeFileContent(fullPath, `// Content for ${relativePath}`);
|
|
253
|
+
|
|
254
|
+
snapshotManager.updateFileEntry(snapshot, relativePath, {
|
|
255
|
+
path: fullPath,
|
|
256
|
+
url: `automerge:${relativePath.replace(/[\/\.]/g, "-")}` as any,
|
|
257
|
+
head: [`head-${relativePath}`] as any,
|
|
258
|
+
extension: path.extname(relativePath).slice(1),
|
|
259
|
+
mimeType: "text/typescript",
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
console.log(`๐ Created: ${relativePath}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
expect(snapshot.files.size).toBe(5);
|
|
266
|
+
|
|
267
|
+
// Delete entire src directory
|
|
268
|
+
await removePath(path.join(testDir, "src"));
|
|
269
|
+
console.log(`๐๏ธ Deleted entire src/ directory tree`);
|
|
270
|
+
|
|
271
|
+
// Verify all files and directories are gone
|
|
272
|
+
for (const relativePath of testStructure) {
|
|
273
|
+
const fullPath = path.join(testDir, relativePath);
|
|
274
|
+
expect(await pathExists(fullPath)).toBe(false);
|
|
275
|
+
}
|
|
276
|
+
expect(await pathExists(path.join(testDir, "src"))).toBe(false);
|
|
277
|
+
|
|
278
|
+
// Simulate snapshot cleanup for all files
|
|
279
|
+
for (const relativePath of testStructure) {
|
|
280
|
+
snapshotManager.removeFileEntry(snapshot, relativePath);
|
|
281
|
+
console.log(`๐ธ Removed from snapshot: ${relativePath}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
expect(snapshot.files.size).toBe(0);
|
|
285
|
+
console.log(`โ
Directory tree deletion handled correctly`);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe("Error Recovery and Edge Cases", () => {
|
|
290
|
+
it("should handle deletion of non-existent files gracefully", async () => {
|
|
291
|
+
console.log("\n๐งช Testing Non-Existent File Deletion");
|
|
292
|
+
|
|
293
|
+
const nonExistentPath = path.join(testDir, "never-existed.ts");
|
|
294
|
+
|
|
295
|
+
// Attempt to delete non-existent file (should not throw)
|
|
296
|
+
await expect(removePath(nonExistentPath)).resolves.not.toThrow();
|
|
297
|
+
console.log(`โ
Non-existent file deletion handled gracefully`);
|
|
298
|
+
|
|
299
|
+
// Attempt to remove from snapshot (should not throw)
|
|
300
|
+
const snapshot = snapshotManager.createEmpty();
|
|
301
|
+
expect(() => {
|
|
302
|
+
snapshotManager.removeFileEntry(snapshot, "never-existed.ts");
|
|
303
|
+
}).not.toThrow();
|
|
304
|
+
console.log(`โ
Non-existent snapshot entry removal handled gracefully`);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("should provide debugging info for deletion failures", async () => {
|
|
308
|
+
console.log("\n๐งช Deletion Debugging Information");
|
|
309
|
+
|
|
310
|
+
const debugFilePath = path.join(testDir, "debug-deletion.ts");
|
|
311
|
+
const content = "interface Debug { info: string; }";
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
// Create file
|
|
315
|
+
await writeFileContent(debugFilePath, content);
|
|
316
|
+
console.log(`๐ File created: ${debugFilePath}`);
|
|
317
|
+
console.log(`๐ File size: ${content.length} characters`);
|
|
318
|
+
|
|
319
|
+
// Verify file exists and is readable
|
|
320
|
+
const readBack = await readFileContent(debugFilePath);
|
|
321
|
+
console.log(`๐ File readable: ${typeof readBack === "string"}`);
|
|
322
|
+
console.log(`โ
Content matches: ${readBack === content}`);
|
|
323
|
+
|
|
324
|
+
// Delete file
|
|
325
|
+
const deleteStart = Date.now();
|
|
326
|
+
await removePath(debugFilePath);
|
|
327
|
+
const deleteTime = Date.now() - deleteStart;
|
|
328
|
+
|
|
329
|
+
console.log(`๐๏ธ Deletion completed in ${deleteTime}ms`);
|
|
330
|
+
console.log(
|
|
331
|
+
`๐ File exists after deletion: ${await pathExists(debugFilePath)}`
|
|
332
|
+
);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error(`โ Deletion test failed:`, error);
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
});
|