scripts/smoke.js - git - Webernet Explorer _X
git 🏠 Home 👤 Sign up Self hosted git, retro shell
★ otgit, code hosting from the year 2000 ★
📄 octuna / scripts / smoke.js
228 lines · 8.2 KB · Raw
1// End-to-end smoke test. Requires a running server and git in PATH.
2// Creates test user + repo, exercises every route, cleans up.
3// Run: node scripts/smoke.js
4 
5'use strict';
6 
7const http = require('http');
8const https = require('https');
9const { spawnSync } = require('child_process');
10const fs = require('fs');
11const path = require('path');
12const os = require('os');
13 
14const config = require('../lib/config');
15const { JsonStore } = require('../lib/store');
16 
17const BASE = config.get('site.publicUrl').replace(/\/$/, '');
18const USE_HTTPS = BASE.startsWith('https');
19const GIT = config.get('git.binPath') || 'git';
20const TEST_USER = 'smoketestuser99';
21const TEST_PASS = 'smoketestpass123';
22const TEST_REPO = 'smoketestrepo99';
23 
24let passed = 0;
25let failed = 0;
26let cookie = '';
27 
28function assert(label, condition, detail) {
29 if (condition) {
30 console.log(' PASS', label);
31 passed++;
32 } else {
33 console.error(' FAIL', label, detail || '');
34 failed++;
35 }
36}
37 
38function req(method, urlPath, body, extraHeaders) {
39 return new Promise((resolve, reject) => {
40 const url = new URL(BASE + urlPath);
41 const opts = {
42 method,
43 hostname: url.hostname,
44 port: url.port || (USE_HTTPS ? 443 : 80),
45 path: url.pathname + url.search,
46 headers: {
47 'Content-Type': 'application/json',
48 ...(cookie ? { Cookie: cookie } : {}),
49 ...(extraHeaders || {})
50 }
51 };
52 const transport = USE_HTTPS ? https : http;
53 const r = transport.request(opts, res => {
54 const chunks = [];
55 res.on('data', d => chunks.push(d));
56 res.on('end', () => {
57 const raw = Buffer.concat(chunks).toString();
58 let json = null;
59 try { json = JSON.parse(raw); } catch {}
60 const setCookie = res.headers['set-cookie'];
61 if (setCookie) {
62 const match = setCookie.join(';').match(/otgit_sid=[^;]+/);
63 if (match) cookie = match[0];
64 }
65 resolve({ status: res.statusCode, json, raw, headers: res.headers });
66 });
67 });
68 r.on('error', reject);
69 if (body) r.write(JSON.stringify(body));
70 r.end();
71 });
72}
73 
74function gitCmd(args, cwd) {
75 const r = spawnSync(GIT, args, { cwd, encoding: 'utf8', timeout: 15000 });
76 return { ok: r.status === 0, stdout: r.stdout, stderr: r.stderr };
77}
78 
79function cleanup() {
80 const dataDir = path.join(__dirname, '..', 'data');
81 const usersPath = path.join(dataDir, 'users.json');
82 const reposPath = path.join(dataDir, 'repos.json');
83 const repoDir = path.join(dataDir, 'repos', TEST_USER);
84 
85 try {
86 const users = JSON.parse(fs.readFileSync(usersPath, 'utf8'));
87 delete users[TEST_USER];
88 fs.writeFileSync(usersPath, JSON.stringify(users));
89 } catch {}
90 
91 try {
92 const repos = JSON.parse(fs.readFileSync(reposPath, 'utf8'));
93 delete repos[`${TEST_USER}/${TEST_REPO}`];
94 fs.writeFileSync(reposPath, JSON.stringify(repos));
95 } catch {}
96 
97 try {
98 fs.rmSync(repoDir, { recursive: true, force: true });
99 } catch {}
100}
101 
102async function run() {
103 console.log('otgit smoke test');
104 console.log('target:', BASE);
105 console.log('');
106 
107 cleanup();
108 cookie = '';
109 
110 console.log('[health]');
111 const health = await req('GET', '/health');
112 assert('GET /health = 200', health.status === 200);
113 assert('GET /health ok:true', health.json && health.json.ok === true);
114 
115 console.log('[static]');
116 const css = await req('GET', '/style.css');
117 assert('GET /style.css = 200', css.status === 200);
118 
119 console.log('[home]');
120 const home = await req('GET', '/');
121 assert('GET / = 200', home.status === 200);
122 
123 console.log('[signup page]');
124 const signupPage = await req('GET', '/signup');
125 assert('GET /signup = 200', signupPage.status === 200);
126 
127 console.log('[auth api]');
128 const signup = await req('POST', '/api/signup', { username: TEST_USER, password: TEST_PASS });
129 assert('POST /api/signup = 201', signup.status === 201, signup.json);
130 assert('POST /api/signup ok', signup.json && signup.json.ok);
131 
132 const me = await req('GET', '/api/me');
133 assert('GET /api/me returns user', me.json && me.json.user && me.json.user.username === TEST_USER);
134 
135 const loginOther = await req('POST', '/api/login', { username: TEST_USER, password: 'wrong' });
136 assert('POST /api/login wrong password = 401', loginOther.status === 401);
137 
138 const login = await req('POST', '/api/login', { username: TEST_USER, password: TEST_PASS });
139 assert('POST /api/login = 200', login.status === 200);
140 
141 console.log('[repo create]');
142 const create = await req('POST', '/api/repo/create', { name: TEST_REPO, description: 'smoke test', visibility: 'public' });
143 assert('POST /api/repo/create = 201', create.status === 201, create.json);
144 
145 console.log('[repo pages (empty)]');
146 const repoHome = await req('GET', `/${TEST_USER}/${TEST_REPO}`);
147 assert(`GET /${TEST_USER}/${TEST_REPO} = 200`, repoHome.status === 200);
148 
149 console.log('[git push]');
150 const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'otgit-smoke-'));
151 const repoUrl = BASE + `/${TEST_USER}/${TEST_REPO}.git`;
152 const authUrl = repoUrl.replace('://', `://${TEST_USER}:${TEST_PASS}@`);
153 
154 const cloneResult = gitCmd(['clone', authUrl, tmpDir], os.tmpdir());
155 assert('git clone (empty)', cloneResult.ok, cloneResult.stderr);
156 
157 if (cloneResult.ok) {
158 fs.writeFileSync(path.join(tmpDir, 'README.md'), `# ${TEST_REPO}\n\nSmoke test repo.\n`);
159 fs.writeFileSync(path.join(tmpDir, 'hello.txt'), 'hello world\n');
160 gitCmd(['config', 'user.email', 'smoke@test.local'], tmpDir);
161 gitCmd(['config', 'user.name', 'Smoke Test'], tmpDir);
162 gitCmd(['add', '.'], tmpDir);
163 gitCmd(['commit', '-m', 'initial commit'], tmpDir);
164 const push = gitCmd(['push', 'origin', 'HEAD:main'], tmpDir);
165 assert('git push', push.ok, push.stderr);
166 }
167 
168 console.log('[repo pages (with content)]');
169 const repoHome2 = await req('GET', `/${TEST_USER}/${TEST_REPO}`);
170 assert(`GET /${TEST_USER}/${TEST_REPO} after push = 200`, repoHome2.status === 200);
171 
172 const blobPage = await req('GET', `/${TEST_USER}/${TEST_REPO}/blob/main/README.md`);
173 assert('GET blob/main/README.md = 200', blobPage.status === 200);
174 
175 const rawFile = await req('GET', `/${TEST_USER}/${TEST_REPO}/raw/main/README.md`);
176 assert('GET raw/main/README.md = 200', rawFile.status === 200);
177 assert('raw file has content', rawFile.raw.includes('Smoke test repo'));
178 
179 const history = await req('GET', `/${TEST_USER}/${TEST_REPO}/history`);
180 assert('GET history = 200', history.status === 200);
181 
182 console.log('[git clone public]');
183 const tmpDir2 = fs.mkdtempSync(path.join(os.tmpdir(), 'otgit-smoke2-'));
184 const clone2 = gitCmd(['clone', repoUrl, tmpDir2], os.tmpdir());
185 assert('git clone public (no auth)', clone2.ok, clone2.stderr);
186 
187 console.log('[repo settings]');
188 const settingsPage = await req('GET', `/${TEST_USER}/${TEST_REPO}/settings`);
189 assert('GET settings page = 200', settingsPage.status === 200);
190 
191 const updateDesc = await req('POST', `/api/repo/${TEST_USER}/${TEST_REPO}/settings`, { description: 'updated' });
192 assert('POST settings (description) = 200', updateDesc.status === 200, updateDesc.json);
193 
194 const rename = await req('POST', `/api/repo/${TEST_USER}/${TEST_REPO}/rename`, { name: TEST_REPO + 'v2' });
195 assert('POST rename = 200', rename.status === 200, rename.json);
196 
197 const delRepo = await req('POST', `/api/repo/${TEST_USER}/${TEST_REPO + 'v2'}/delete`, {});
198 assert('POST delete = 200', delRepo.status === 200, delRepo.json);
199 
200 console.log('[user profile]');
201 const profile = await req('GET', `/${TEST_USER}`);
202 assert(`GET /${TEST_USER} = 200`, profile.status === 200);
203 
204 console.log('[logout]');
205 const logout = await req('POST', '/api/logout');
206 assert('POST /api/logout = 200', logout.status === 200);
207 
208 const meAfter = await req('GET', '/api/me');
209 assert('GET /api/me after logout = null user', meAfter.json && meAfter.json.user === null);
210 
211 console.log('[not found]');
212 const nf = await req('GET', '/no-such-page-here');
213 assert('unknown route = 404', nf.status === 404);
214 
215 console.log('');
216 cleanup();
217 try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
218 try { fs.rmSync(tmpDir2 || '', { recursive: true, force: true }); } catch {}
219 
220 console.log(`Results: ${passed} passed, ${failed} failed`);
221 if (failed > 0) process.exit(1);
222}
223 
224run().catch(e => {
225 console.error('smoke test error:', e.message);
226 process.exit(1);
227});
228 
Done 🔒 Internet