@apollo-annotation/cli 0.3.1 → 0.3.3

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.
@@ -0,0 +1,978 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
4
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
5
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
6
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
7
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
8
+ /**
9
+ * USAGE
10
+ * From package root directory (`packages/apollo-cli`). Run all tests:
11
+ *
12
+ * yarn test:cli
13
+ *
14
+ * Run only matching pattern:
15
+ *
16
+ * yarn test:cli --test-name-pattern='Print help|Feature get'
17
+ */
18
+ import assert from 'node:assert';
19
+ import { before, beforeEach, afterEach, describe } from 'node:test';
20
+ import { Shell } from './utils.js';
21
+ import fs from 'node:fs';
22
+ import * as crypto from 'node:crypto';
23
+ import path from 'node:path';
24
+ const apollo = 'yarn dev';
25
+ const P = '--profile testAdmin';
26
+ let configFile = '';
27
+ let configFileBak = '';
28
+ void describe('Test CLI', () => {
29
+ before(() => {
30
+ configFile = new Shell(`${apollo} config --get-config-file`).stdout.trim();
31
+ configFileBak = `${path.basename(configFile)}.bak`;
32
+ if (fs.existsSync(configFileBak)) {
33
+ throw new Error(`Backup config file ${configFileBak} already exists. If safe to do so, delete it before testing`);
34
+ }
35
+ new Shell(`${apollo} config ${P} address http://localhost:3999`);
36
+ new Shell(`${apollo} config ${P} accessType root`);
37
+ new Shell(`${apollo} config ${P} rootPassword pass`);
38
+ new Shell(`${apollo} login ${P} -f`);
39
+ });
40
+ beforeEach(() => {
41
+ // Backup starting config file
42
+ fs.copyFileSync(configFile, configFileBak);
43
+ });
44
+ afterEach(() => {
45
+ // Put back starting config file
46
+ fs.renameSync(configFileBak, configFile);
47
+ });
48
+ void globalThis.itName('Print help', () => {
49
+ const p = new Shell(`${apollo} --help`);
50
+ assert.ok(p.stdout.includes('COMMANDS'));
51
+ });
52
+ void globalThis.itName('Get config file', () => {
53
+ const p = new Shell(`${apollo} config --get-config-file`);
54
+ assert.ok(p.stdout.startsWith('/'));
55
+ });
56
+ void globalThis.itName('Config invalid keys', () => {
57
+ let p = new Shell(`${apollo} config ${P} address spam`, false);
58
+ assert.strictEqual(1, p.returncode);
59
+ assert.ok(p.stderr.includes('Invalid setting:'));
60
+ p = new Shell(`${apollo} config ${P} ADDRESS http://localhost:3999`, false);
61
+ assert.strictEqual(1, p.returncode);
62
+ assert.ok(p.stderr.includes('Invalid setting:'));
63
+ p = new Shell(`${apollo} config ${P} accessType spam`, false);
64
+ assert.strictEqual(1, p.returncode);
65
+ assert.ok(p.stderr.includes('Invalid setting:'));
66
+ });
67
+ void globalThis.itName('Can change access type', () => {
68
+ const p = new Shell(`${apollo} config ${P} accessType google`);
69
+ assert.strictEqual('', p.stdout.trim());
70
+ });
71
+ void globalThis.itName('Apollo status', () => {
72
+ let p = new Shell(`${apollo} status ${P}`);
73
+ assert.strictEqual(p.stdout.trim(), 'testAdmin: Logged in');
74
+ new Shell(`${apollo} logout ${P}`);
75
+ p = new Shell(`${apollo} status ${P}`);
76
+ assert.strictEqual(p.stdout.trim(), 'testAdmin: Logged out');
77
+ new Shell(`${apollo} login ${P} -f`);
78
+ });
79
+ void globalThis.itName('Feature get', () => {
80
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv1 -f`);
81
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv2 -f`);
82
+ let p = new Shell(`${apollo} feature get ${P} -a vv1`);
83
+ assert.ok(p.stdout.includes('ctgA'));
84
+ assert.ok(p.stdout.includes('SomeContig'));
85
+ p = new Shell(`${apollo} feature get ${P} -r ctgA`, false);
86
+ assert.ok(p.returncode != 0);
87
+ assert.ok(p.stderr.includes('found in more than one assembly'));
88
+ p = new Shell(`${apollo} feature get ${P} -a vv1 -r ctgA`);
89
+ let out = JSON.parse(p.stdout);
90
+ assert.ok(Object.keys(out.at(0)).length > 2);
91
+ p = new Shell(`${apollo} feature get ${P} -a vv1 -r ctgA -s 40 -e 41`);
92
+ out = JSON.parse(p.stdout);
93
+ assert.strictEqual(out.length, 1);
94
+ p = new Shell(`${apollo} feature get ${P} -a vv1 -r ctgA -s 1000 -e 1000`);
95
+ out = JSON.parse(p.stdout);
96
+ assert.deepStrictEqual(out, []);
97
+ p = new Shell(`${apollo} feature get ${P} -r FOOBAR`);
98
+ out = JSON.parse(p.stdout);
99
+ assert.deepStrictEqual(out, []);
100
+ p = new Shell(`${apollo} feature get ${P} -a FOOBAR -r ctgA`, false);
101
+ assert.ok(p.returncode != 0);
102
+ assert.ok(p.stderr.includes('returned 0 assemblies'));
103
+ });
104
+ void globalThis.itName('Assembly get', () => {
105
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a vv1 -e -f`);
106
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a vv2 -e -f`);
107
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a vv3 -e -f`);
108
+ let p = new Shell(`${apollo} assembly get ${P}`);
109
+ assert.ok(p.stdout.includes('vv1'));
110
+ assert.ok(p.stdout.includes('vv2'));
111
+ assert.ok(p.stdout.includes('vv3'));
112
+ p = new Shell(`${apollo} assembly get ${P} -a vv1 vv2`);
113
+ assert.ok(p.stdout.includes('vv1'));
114
+ assert.ok(p.stdout.includes('vv2'));
115
+ assert.ok(p.stdout.includes('vv3') == false);
116
+ const out = JSON.parse(p.stdout);
117
+ const aid = out.filter((x) => x.name === 'vv1').at(0)._id;
118
+ p = new Shell(`${apollo} assembly get ${P} -a ${aid} vv2`);
119
+ assert.ok(p.stdout.includes('vv1'));
120
+ assert.ok(p.stdout.includes('vv2'));
121
+ assert.ok(p.stdout.includes('vv3') == false);
122
+ });
123
+ void globalThis.itName('Delete assembly', () => {
124
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a volvox1 -f`);
125
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a volvox2 -f`);
126
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a volvox3 -f`);
127
+ let p = new Shell(`${apollo} assembly get ${P} | jq '.[] | select(.name == "volvox1") | ._id'`);
128
+ const aid = p.stdout.trim();
129
+ p = new Shell(`${apollo} assembly delete ${P} -v -a ${aid} volvox2 volvox2`);
130
+ const out = JSON.parse(p.stdout);
131
+ assert.strictEqual(out.length, 2);
132
+ assert.ok(p.stderr.includes('2 '));
133
+ new Shell(`${apollo} assembly delete ${P} -a ${aid} volvox2`);
134
+ p = new Shell(`${apollo} assembly get ${P}`);
135
+ assert.ok(p.stdout.includes(aid) == false);
136
+ assert.ok(p.stdout.includes('volvox1') == false);
137
+ assert.ok(p.stdout.includes('volvox2') == false);
138
+ assert.ok(p.stdout.includes('volvox3'));
139
+ });
140
+ void globalThis.itName('Id reader', () => {
141
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a v1 -f`);
142
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a v2 -f`);
143
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a v3 -f`);
144
+ let p = new Shell(`${apollo} assembly get ${P}`);
145
+ const xall = JSON.parse(p.stdout);
146
+ p = new Shell(`${apollo} assembly get ${P} -a v1 v2`);
147
+ let out = JSON.parse(p.stdout);
148
+ assert.strictEqual(out.length, 2);
149
+ // This is interpreted as an assembly named 'v1 v2'
150
+ p = new Shell(`echo v1 v2 | ${apollo} assembly get ${P} -a -`);
151
+ out = JSON.parse(p.stdout);
152
+ assert.strictEqual(out.length, 0);
153
+ // These are two assemblies
154
+ p = new Shell(`echo -e 'v1 \n v2' | ${apollo} assembly get ${P} -a -`);
155
+ out = JSON.parse(p.stdout);
156
+ assert.strictEqual(out.length, 2);
157
+ p = new Shell(`${apollo} assembly get ${P} | ${apollo} assembly get ${P} -a -`);
158
+ out = JSON.parse(p.stdout);
159
+ assert.ok(out.length >= 3);
160
+ // From json file
161
+ new Shell(`${apollo} assembly get ${P} > test_data/tmp.json`);
162
+ p = new Shell(`${apollo} assembly get ${P} -a test_data/tmp.json`);
163
+ out = JSON.parse(p.stdout);
164
+ assert.ok(out.length >= 3);
165
+ fs.unlinkSync('test_data/tmp.json');
166
+ // From text file, one name or id per line
167
+ fs.writeFileSync('test_data/tmp.txt', 'v1 \n v2 \r\n v3 \n');
168
+ p = new Shell(`${apollo} assembly get ${P} -a test_data/tmp.txt`);
169
+ out = JSON.parse(p.stdout);
170
+ assert.strictEqual(out.length, 3);
171
+ fs.unlinkSync('test_data/tmp.txt');
172
+ // From json string
173
+ const aid = xall.at(0)._id;
174
+ let j = `{"_id": "${aid}"}`;
175
+ p = new Shell(`${apollo} assembly get ${P} -a '${j}'`);
176
+ out = JSON.parse(p.stdout);
177
+ assert.strictEqual(out.length, 1);
178
+ assert.strictEqual(out.at(0)._id, aid);
179
+ const id1 = xall.at(0)._id;
180
+ const id2 = xall.at(1)._id;
181
+ j = `[{"_id": "${id1}"}, {"_id": "${id2}"}]`;
182
+ p = new Shell(`${apollo} assembly get ${P} -a '${j}'`);
183
+ out = JSON.parse(p.stdout);
184
+ assert.strictEqual(out.length, 2);
185
+ j = `{"XYZ": "${aid}"}`;
186
+ p = new Shell(`${apollo} assembly get ${P} -a '${j}'`);
187
+ out = JSON.parse(p.stdout);
188
+ assert.strictEqual(out.length, 0);
189
+ p = new Shell(`${apollo} assembly get ${P} -a '[...'`);
190
+ out = JSON.parse(p.stdout);
191
+ assert.strictEqual(out.length, 0);
192
+ });
193
+ void globalThis.itName('Add assembly from gff', () => {
194
+ let p = new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv1 --omit-features -f`);
195
+ const out = JSON.parse(p.stdout);
196
+ assert.ok(Object.keys(out.fileIds).includes('fa'));
197
+ // Get id of assembly named vv1 and check there are no features
198
+ p = new Shell(`${apollo} assembly get ${P} -a vv1`);
199
+ assert.ok(p.stdout.includes('vv1'));
200
+ assert.ok(p.stdout.includes('vv2') == false);
201
+ const asm_id = JSON.parse(p.stdout).at(0)._id;
202
+ p = new Shell(`${apollo} refseq get ${P}`);
203
+ const refseq = JSON.parse(p.stdout.trim());
204
+ const vv1ref = refseq.filter((x) => x.assembly === asm_id);
205
+ const refseq_id = vv1ref.filter((x) => x.name === 'ctgA').at(0)._id;
206
+ p = new Shell(`${apollo} feature get ${P} -r ${refseq_id}`);
207
+ const ff = JSON.parse(p.stdout);
208
+ assert.deepStrictEqual(ff, []);
209
+ p = new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv1`, false);
210
+ assert.ok(p.returncode != 0);
211
+ assert.ok(p.stderr.includes('Error: Assembly "vv1" already exists'));
212
+ // Default assembly name
213
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -f`);
214
+ p = new Shell(`${apollo} assembly get ${P} -a tiny.fasta.gff3`);
215
+ assert.ok(p.stdout.includes('tiny.fasta.gff3'));
216
+ });
217
+ void globalThis.itName('Add assembly large input', () => {
218
+ fs.writeFileSync('test_data/tmp.fa', '>chr1\n');
219
+ const stream = fs.createWriteStream('test_data/tmp.fa', { flags: 'a' });
220
+ for (let i = 0; i < 10_000; i++) {
221
+ stream.write('CATTGTTGCGGAGTTGAACAACGGCATTAGGAACACTTCCGTCTC\n');
222
+ }
223
+ stream.close();
224
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tmp.fa -a test -e -f`, true, 60_000);
225
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tmp.fa -a test -f`, false, 60_000);
226
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tmp.fa -a test -e -f`, true, 60_000);
227
+ fs.unlinkSync('test_data/tmp.fa');
228
+ });
229
+ void globalThis.itName('FIXME Checks are triggered and resolved', () => {
230
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/checks.gff -f`);
231
+ let p = new Shell(`${apollo} feature get ${P} -a checks.gff`);
232
+ const out = JSON.parse(p.stdout);
233
+ p = new Shell(`${apollo} feature check ${P} -a checks.gff`);
234
+ assert.deepStrictEqual(p.stdout.trim(), '[]'); // No failing check
235
+ // Get the ID of the CDS. We need need it to modify the CDS coordinates
236
+ const gene = out.filter((x) => JSON.stringify(x.attributes.gff_id) === JSON.stringify(['gene01']));
237
+ const mrna = Object.values(gene.at(0).children).at(0);
238
+ const cds = Object.values(mrna.children).find((x) => x.attributes.gff_id.at(0) === 'cds01');
239
+ const cds_id = cds._id;
240
+ // Introduce problems
241
+ new Shell(`${apollo} feature edit-coords ${P} -i ${cds_id} --start 4 --end 24`);
242
+ p = new Shell(`${apollo} feature check ${P} -a checks.gff`);
243
+ const checks = JSON.parse(p.stdout);
244
+ // FIXME: There should be 2 failing checks, not 3
245
+ assert.strictEqual(checks.length, 3);
246
+ assert.ok(p.stdout.includes('InternalStopCodonCheck'));
247
+ assert.ok(p.stdout.includes('MissingStopCodonCheck'));
248
+ // Problems fixed
249
+ new Shell(`${apollo} feature edit-coords ${P} -i ${cds_id} --start 16 --end 27`);
250
+ p = new Shell(`${apollo} feature check ${P} -a checks.gff`);
251
+ // FIXME: After fixing, how many warnings do we expect?
252
+ assert.deepStrictEqual(JSON.parse(p.stdout).length, 4);
253
+ });
254
+ void globalThis.itName('Add assembly from local fasta', () => {
255
+ let p = new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a vv1 -e -f`);
256
+ const out = JSON.parse(p.stdout);
257
+ assert.ok(Object.keys(out.fileIds).includes('fa'));
258
+ p = new Shell(`${apollo} assembly get ${P} -a vv1`);
259
+ assert.ok(p.stdout.includes('vv1'));
260
+ p = new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a vv1 -e`, false);
261
+ assert.ok(p.returncode != 0);
262
+ assert.ok(p.stderr.includes('Error: Assembly "vv1" already exists'));
263
+ p = new Shell(`${apollo} assembly add-from-fasta ${P} na.fa -a vv1 -e -f`, false);
264
+ assert.ok(p.returncode != 0);
265
+ assert.ok(p.stderr.includes('Input'));
266
+ // Test default name
267
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -e -f`);
268
+ p = new Shell(`${apollo} assembly get ${P} -a tiny.fasta`);
269
+ assert.ok(p.stdout.includes('tiny.fasta'));
270
+ });
271
+ void globalThis.itName('Add assembly from external fasta', () => {
272
+ let p = new Shell(`${apollo} assembly add-from-fasta ${P} -a vv1 -f http://localhost:3131/volvox.fa.gz`);
273
+ const out = JSON.parse(p.stdout);
274
+ assert.ok(Object.keys(out.externalLocation).includes('fa'));
275
+ p = new Shell(`${apollo} assembly get ${P} -a vv1`);
276
+ assert.ok(p.stdout.includes('vv1'));
277
+ p = new Shell(`${apollo} assembly sequence ${P} -a vv1 -r ctgA -s 1 -e 10`);
278
+ const seq = p.stdout.trim().split('\n');
279
+ assert.strictEqual(seq[1], 'cattgttgcg');
280
+ p = new Shell(`${apollo} assembly add-from-fasta ${P} -a vv1 -f https://x.fa.gz --fai https://x.fa.gz.fai --gzi https://x.fa.gz.gzi`, false);
281
+ assert.ok(p.returncode != 0);
282
+ });
283
+ void globalThis.itName('Detect missing external index', () => {
284
+ const p = new Shell(`${apollo} assembly add-from-fasta ${P} -a vv1 -f https://raw.githubusercontent.com/GMOD/Apollo3/refs/heads/main/packages/apollo-cli/test_data/tiny.fasta`, false);
285
+ assert.ok(p.returncode != 0);
286
+ assert.ok(p.stderr.includes('Index file') && p.stderr.includes('does not exist'));
287
+ });
288
+ void globalThis.itName('Editable sequence not allowed with external source', () => {
289
+ const cmd = `${apollo} assembly add-from-fasta ${P} -a vv1 -f https://raw.githubusercontent.com/GMOD/Apollo3/refs/heads/main/packages/apollo-cli/test_data/tiny.fasta.gz`;
290
+ new Shell(cmd);
291
+ const p = new Shell(`${cmd} -e`, false);
292
+ assert.ok(p.returncode != 0);
293
+ assert.ok(p.stderr.includes('External fasta files are not editable'));
294
+ });
295
+ void globalThis.itName('Edit feature from json', () => {
296
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv1 -f`);
297
+ let p = new Shell(`${apollo} feature search ${P} -a vv1 -t BAC`);
298
+ let out = JSON.parse(p.stdout).at(0);
299
+ assert.strictEqual(out.type, 'BAC');
300
+ p = new Shell(`${apollo} assembly get ${P} -a vv1`);
301
+ const asm_id = JSON.parse(p.stdout).at(0)._id;
302
+ const req = [
303
+ {
304
+ typeName: 'TypeChange',
305
+ changedIds: [out._id],
306
+ assembly: asm_id,
307
+ featureId: out._id,
308
+ oldType: 'BAC',
309
+ newType: 'G_quartet',
310
+ },
311
+ ];
312
+ const j = JSON.stringify(req);
313
+ new Shell(`echo '${j}' | ${apollo} feature edit ${P} -j -`);
314
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t G_quartet`);
315
+ out = JSON.parse(p.stdout).at(0);
316
+ assert.strictEqual(out.type, 'G_quartet');
317
+ });
318
+ void globalThis.itName('Edit feature type', () => {
319
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv1 -f`);
320
+ // Get id of assembly named vv1
321
+ let p = new Shell(`${apollo} assembly get ${P} -a vv1`);
322
+ const asm_id = JSON.parse(p.stdout).at(0)._id;
323
+ // Get refseqs in assembly vv1
324
+ p = new Shell(`${apollo} refseq get ${P} | jq '.[] | select(.assembly == "${asm_id}" and .name == "ctgA") | ._id'`);
325
+ const refseq = p.stdout.trim();
326
+ // Get feature in vv1
327
+ p = new Shell(`${apollo} feature get ${P} -r ${refseq}`);
328
+ const features = JSON.parse(p.stdout);
329
+ assert.ok(features.length > 2);
330
+ // Get id of feature of type contig
331
+ let contig = features.filter((x) => x.type === 'contig');
332
+ assert.strictEqual(contig.length, 1);
333
+ const contig_id = contig.at(0)._id;
334
+ // Edit type of "contig" feature
335
+ p = new Shell(`${apollo} feature edit-type ${P} -i ${contig_id} -t region`);
336
+ p = new Shell(`${apollo} feature get ${P} -r ${refseq} | jq '.[] | select(._id == "${contig_id}")'`);
337
+ contig = JSON.parse(p.stdout);
338
+ assert.deepStrictEqual(contig.type, 'region');
339
+ // Return current type
340
+ p = new Shell(`${apollo} feature edit-type ${P} -i ${contig_id}`);
341
+ assert.deepStrictEqual(p.stdout.trim(), 'region');
342
+ });
343
+ void globalThis.itName('Edit feature coords', () => {
344
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv1 -f`);
345
+ // Get id of assembly named vv1
346
+ let p = new Shell(`${apollo} assembly get ${P} -a vv1`);
347
+ const asm_id = JSON.parse(p.stdout).at(0)._id;
348
+ // Get refseqs in assembly vv1
349
+ p = new Shell(`${apollo} refseq get ${P} | jq '.[] | select(.assembly == "${asm_id}" and .name == "ctgA") | ._id'`);
350
+ const refseq = p.stdout.trim();
351
+ // Get feature in vv1
352
+ p = new Shell(`${apollo} feature get ${P} -r ${refseq}`);
353
+ const features = JSON.parse(p.stdout);
354
+ assert.ok(features.length > 2);
355
+ // Get id of feature of type contig
356
+ let contig = features.filter((x) => x.type === 'contig');
357
+ assert.strictEqual(contig.length, 1);
358
+ const contig_id = contig.at(0)._id;
359
+ // Edit start and end coordinates
360
+ new Shell(`${apollo} feature edit-coords ${P} -i ${contig_id} -s 80 -e 160`);
361
+ new Shell(`${apollo} feature edit-coords ${P} -i ${contig_id} -s 20 -e 100`);
362
+ p = new Shell(`${apollo} feature get ${P} -r ${refseq} | jq '.[] | select(._id == "${contig_id}")'`);
363
+ contig = JSON.parse(p.stdout);
364
+ assert.strictEqual(contig.min, 20 - 1);
365
+ assert.strictEqual(contig.max, 100);
366
+ p = new Shell(`${apollo} feature edit-coords ${P} -i ${contig_id} -s 1 -e 1`);
367
+ p = new Shell(`${apollo} feature get ${P} -r ${refseq} | jq '.[] | select(._id == "${contig_id}")'`);
368
+ contig = JSON.parse(p.stdout);
369
+ assert.strictEqual(contig.min, 0);
370
+ assert.strictEqual(contig.max, 1);
371
+ p = new Shell(`${apollo} feature edit-coords ${P} -i ${contig_id} -s 0`, false);
372
+ assert.strictEqual(p.returncode, 2);
373
+ assert.ok(p.stderr.includes('Coordinates must be greater than 0'));
374
+ p = new Shell(`${apollo} feature edit-coords ${P} -i ${contig_id} -s 10 -e 9`, false);
375
+ assert.strictEqual(p.returncode, 2);
376
+ assert.ok(p.stderr.includes('Error: The new end coordinate is lower than the new start coordinate'));
377
+ // Edit a feature by extending beyond the boundary of its parent and
378
+ // check it throws a meaningful error message
379
+ // let eden_gene = undefined
380
+ const eden_gene = features
381
+ .filter((x) => x.type === 'gene' && x.attributes.gff_name.at(0) === 'EDEN')
382
+ .at(0);
383
+ assert.ok(eden_gene);
384
+ const mrna_id = Object.keys(eden_gene.children).at(0);
385
+ p = new Shell(`${apollo} feature edit-coords ${P} -i ${mrna_id} -s 1`, false);
386
+ assert.ok(p.returncode != 0);
387
+ assert.ok(p.stderr.includes('exceeds the bounds of its parent'));
388
+ });
389
+ void globalThis.itName('Edit attributes', () => {
390
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv1 -f`);
391
+ // Get id of assembly named vv1
392
+ let p = new Shell(`${apollo} assembly get ${P} -a vv1`);
393
+ const asm_id = JSON.parse(p.stdout).at(0)._id;
394
+ p = new Shell(`${apollo} refseq get ${P} | jq '.[] | select(.assembly == "${asm_id}" and .name == "ctgA") | ._id'`);
395
+ const refseq = p.stdout.trim();
396
+ // Get feature in vv1
397
+ p = new Shell(`${apollo} feature get ${P} -r ${refseq} | jq '.[] | select(.type == "contig") | ._id'`);
398
+ const fid = p.stdout.trim();
399
+ // Edit existing attribute value
400
+ p = new Shell(`${apollo} feature edit-attribute ${P} -i $${fid} -a source -v 'Eggs & Stuff'`);
401
+ p = new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a source`);
402
+ let out = JSON.parse(p.stdout);
403
+ assert.deepStrictEqual(out.at(0), `Eggs & Stuff`);
404
+ // Add attribute
405
+ new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a newAttr -v stuff`);
406
+ p = new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a newAttr`);
407
+ assert.ok(p.stdout.includes('stuf'));
408
+ // Non existing attr
409
+ p = new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a NonExist`);
410
+ assert.deepStrictEqual(p.stdout.trim(), '');
411
+ // List of values
412
+ p = new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a newAttr -v A B C`);
413
+ p = new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a newAttr`);
414
+ out = JSON.parse(p.stdout);
415
+ assert.deepStrictEqual(out, ['A', 'B', 'C']);
416
+ // Delete attribute
417
+ new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a newAttr -d`);
418
+ p = new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a newAttr`);
419
+ assert.deepStrictEqual(p.stdout.trim(), '');
420
+ // Delete again is ok
421
+ new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a newAttr -d`);
422
+ // Special fields
423
+ p = new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a 'Gene Ontology' -v GO:0051728 GO:0019090`);
424
+ p = new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a 'Gene Ontology'`);
425
+ out = JSON.parse(p.stdout);
426
+ assert.deepStrictEqual(out, ['GO:0051728', 'GO:0019090']);
427
+ // This should fail
428
+ p = new Shell(`${apollo} feature edit-attribute ${P} -i ${fid} -a 'Gene Ontology' -v FOOBAR`);
429
+ });
430
+ void globalThis.itName('Search features', () => {
431
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv1 -f`);
432
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv2 -f`);
433
+ let p = new Shell(`${apollo} feature search ${P} -a vv1 vv2 -t EDEN`);
434
+ let out = JSON.parse(p.stdout);
435
+ assert.strictEqual(out.length, 2);
436
+ assert.ok(p.stdout.includes('EDEN'));
437
+ p = new Shell(`${apollo} feature search ${P} -t EDEN`);
438
+ out = JSON.parse(p.stdout);
439
+ assert.ok(out.length >= 2);
440
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t EDEN`);
441
+ out = JSON.parse(p.stdout);
442
+ assert.strictEqual(out.length, 1);
443
+ assert.ok(p.stdout.includes('EDEN'));
444
+ p = new Shell(`${apollo} feature search ${P} -a foobar -t EDEN`);
445
+ assert.strictEqual('[]', p.stdout.trim());
446
+ assert.ok(p.stderr.includes('Warning'));
447
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t foobarspam`);
448
+ assert.deepStrictEqual(p.stdout.trim(), '[]');
449
+ // It searches attributes values, not attribute names
450
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t multivalue`);
451
+ assert.deepStrictEqual(p.stdout.trim(), '[]');
452
+ // Search feature type
453
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t contig`);
454
+ assert.ok(p.stdout.includes('"type": "contig"'));
455
+ // Search source (which in fact is an attribute)
456
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t someExample`);
457
+ assert.ok(p.stdout.includes('SomeContig'));
458
+ // Case insensitive
459
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t SOMEexample`);
460
+ assert.ok(p.stdout.includes('SomeContig'));
461
+ // No partial word match
462
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t Fingerpri`);
463
+ assert.deepStrictEqual(p.stdout.trim(), '[]');
464
+ // Match full word not necessarily full value
465
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t Fingerprinted`);
466
+ assert.ok(p.stdout.includes('Fingerprinted'));
467
+ // Does not search contig names (reference sequence name)
468
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t ctgB`);
469
+ assert.deepStrictEqual(p.stdout.trim(), '[]');
470
+ // Does not match common words (?) ...
471
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t with`);
472
+ assert.deepStrictEqual(p.stdout.trim(), '[]');
473
+ // ...But "fake" is ok
474
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t fake`);
475
+ assert.ok(p.stdout.includes('FakeSNP1'));
476
+ // ...or a single unusual letter
477
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t Q`);
478
+ assert.ok(p.stdout.includes('"Q"'));
479
+ });
480
+ void globalThis.itName('Delete features', () => {
481
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv1 -f`);
482
+ let p = new Shell(`${apollo} feature search ${P} -a vv1 -t EDEN`);
483
+ const fid = JSON.parse(p.stdout).at(0)._id;
484
+ p = new Shell(`${apollo} feature delete ${P} -i ${fid} --dry-run`);
485
+ assert.ok(p.stdout.includes(fid));
486
+ new Shell(`${apollo} feature delete ${P} -i ${fid}`);
487
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t EDEN`);
488
+ assert.deepStrictEqual(p.stdout.trim(), '[]');
489
+ p = new Shell(`${apollo} feature delete ${P} -i ${fid}`, false);
490
+ assert.strictEqual(p.returncode, 1);
491
+ assert.ok(p.stderr.includes('The following featureId was not found in database'));
492
+ p = new Shell(`${apollo} feature delete ${P} --force -i ${fid}`);
493
+ assert.strictEqual(p.returncode, 0);
494
+ });
495
+ void globalThis.itName('Add child features', () => {
496
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a vv1 -f`);
497
+ let p = new Shell(`${apollo} feature search ${P} -a vv1 -t contig`);
498
+ const fid = JSON.parse(p.stdout).at(0)._id;
499
+ new Shell(`${apollo} feature add-child ${P} -i ${fid} -s 10 -e 20 -t contig_read`);
500
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t contig_read`);
501
+ assert.ok(p.stdout.includes('contig_read'));
502
+ assert.ok(p.stdout.includes('"min": 9'));
503
+ assert.ok(p.stdout.includes('"max": 20'));
504
+ p = new Shell(`${apollo} feature add-child ${P} -i ${fid} -s 10 -e 2000 -t contig_read`, false);
505
+ assert.ok(p.returncode != 0);
506
+ assert.ok(p.stderr.includes('Child feature coordinates'));
507
+ // Should this fail?
508
+ p = new Shell(`${apollo} feature add-child ${P} -i ${fid} -s 10 -e 20 -t FOOBAR`, false);
509
+ assert.strictEqual(p.returncode, 0);
510
+ });
511
+ void globalThis.itName('Import features', () => {
512
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a vv1 -e -f`);
513
+ new Shell(`${apollo} feature import ${P} test_data/tiny.fasta.gff3 -a vv1`);
514
+ let p = new Shell(`${apollo} feature search ${P} -a vv1 -t contig`);
515
+ let out = JSON.parse(p.stdout);
516
+ assert.strictEqual(out.length, 2);
517
+ // Import again: Add to existing feature
518
+ p = new Shell(`${apollo} feature import ${P} test_data/tiny.fasta.gff3 -a vv1`);
519
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t contig`);
520
+ out = JSON.parse(p.stdout);
521
+ assert.strictEqual(out.length, 4);
522
+ // Import again: delete ${P} existing
523
+ p = new Shell(`${apollo} feature import ${P} -d test_data/tiny.fasta.gff3 -a vv1`);
524
+ p = new Shell(`${apollo} feature search ${P} -a vv1 -t contig`);
525
+ out = JSON.parse(p.stdout);
526
+ assert.strictEqual(out.length, 2);
527
+ p = new Shell(`${apollo} assembly delete ${P} -a vv2`);
528
+ p = new Shell(`${apollo} feature import ${P} test_data/tiny.fasta.gff3 -a vv2`, false);
529
+ assert.ok(p.returncode != 0);
530
+ assert.ok(p.stderr.includes('Assembly "vv2" does not exist'));
531
+ p = new Shell(`${apollo} feature import ${P} foo.gff3 -a vv1`, false);
532
+ assert.ok(p.returncode != 0);
533
+ assert.ok(p.stderr.includes('File "foo.gff3" does not exist'));
534
+ });
535
+ void globalThis.itName('Copy feature', () => {
536
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a source -f`);
537
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a dest -e -f`);
538
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a dest2 -e -f`);
539
+ let p = new Shell(`${apollo} feature search ${P} -a source -t contig`);
540
+ const fid = JSON.parse(p.stdout).at(0)._id;
541
+ new Shell(`${apollo} feature copy ${P} -i ${fid} -r ctgA -a dest -s 1`);
542
+ p = new Shell(`${apollo} feature search ${P} -a dest -t contig`);
543
+ let out = JSON.parse(p.stdout).at(0);
544
+ assert.strictEqual(out.min, 0);
545
+ assert.strictEqual(out.max, 50);
546
+ // RefSeq id does not need assembly
547
+ p = new Shell(`${apollo} refseq get ${P} -a dest2`);
548
+ const destRefSeq = JSON.parse(p.stdout)
549
+ .filter((x) => x.name === 'ctgA')
550
+ .at(0)._id;
551
+ p = new Shell(`${apollo} feature copy ${P} -i ${fid} -r ${destRefSeq} -s 2`);
552
+ p = new Shell(`${apollo} feature search ${P} -a dest2 -t contig`);
553
+ out = JSON.parse(p.stdout).at(0);
554
+ assert.strictEqual(out.min, 1);
555
+ assert.strictEqual(out.max, 51);
556
+ // Copy to same assembly
557
+ new Shell(`${apollo} feature copy ${P} -i ${fid} -r ctgA -a source -s 10`);
558
+ p = new Shell(`${apollo} feature search ${P} -a source -t contig`);
559
+ out = JSON.parse(p.stdout);
560
+ // Copy non-existant feature or refseq
561
+ p = new Shell(`${apollo} feature copy ${P} -i FOOBAR -r ctgA -a dest -s 1`, false);
562
+ assert.ok(p.returncode != 0);
563
+ assert.ok(p.stderr.includes('ERROR'));
564
+ p = new Shell(`${apollo} feature copy ${P} -i ${fid} -r FOOBAR -a dest -s 1`, false);
565
+ assert.ok(p.returncode != 0);
566
+ assert.ok(p.stderr.includes('No reference'));
567
+ // Ambiguous refseq
568
+ p = new Shell(`${apollo} feature copy ${P} -i ${fid} -r ctgA -s 1`, false);
569
+ assert.ok(p.returncode != 0);
570
+ assert.ok(p.stderr.includes('more than one'));
571
+ });
572
+ void globalThis.itName('Get changes', () => {
573
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a myAssembly -e -f`);
574
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a yourAssembly -e -f`);
575
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a ourAssembly -e -f`);
576
+ let p = new Shell(`${apollo} change get ${P}`);
577
+ JSON.parse(p.stdout);
578
+ assert.ok(p.stdout.includes('myAssembly'));
579
+ assert.ok(p.stdout.includes('yourAssembly'));
580
+ p = new Shell(`${apollo} change get ${P} -a myAssembly ourAssembly`);
581
+ assert.ok(p.stdout.includes('myAssembly'));
582
+ assert.ok(p.stdout.includes('ourAssembly'));
583
+ assert.ok(p.stdout.includes('yourAssembly') == false);
584
+ // Delete assemblies and get changes by assembly name: Nothing is
585
+ // returned because the assemblies collection doesn't contain that name
586
+ // anymore. Ideally you should still be able to get changes by name?
587
+ new Shell(`${apollo} assembly delete ${P} -a myAssembly yourAssembly ourAssembly`);
588
+ p = new Shell(`${apollo} change get ${P} -a myAssembly`);
589
+ const out = JSON.parse(p.stdout);
590
+ assert.strictEqual(out.length, 0);
591
+ });
592
+ void globalThis.itName('Get sequence', () => {
593
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a v1 -e -f`);
594
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta -a v2 -e -f`);
595
+ let p = new Shell(`${apollo} assembly sequence ${P} -a nonExistant`, false);
596
+ assert.ok(p.returncode != 0);
597
+ assert.ok(p.stderr.includes('returned 0 assemblies'));
598
+ p = new Shell(`${apollo} assembly sequence ${P} -a v1 -s 0`, false);
599
+ assert.ok(p.returncode != 0);
600
+ assert.ok(p.stderr.includes('must be greater than 0'));
601
+ p = new Shell(`${apollo} assembly sequence ${P} -a v1`);
602
+ let seq = p.stdout.trim().split('\n');
603
+ assert.strictEqual(seq.length, 25);
604
+ assert.deepStrictEqual(seq.at(0), '>ctgA:1..420');
605
+ assert.deepStrictEqual(seq.at(1), 'cattgttgcggagttgaacaACGGCATTAGGAACACTTCCGTCTCtcacttttatacgattatgattggttctttagcct');
606
+ assert.deepStrictEqual(seq.at(6), 'ttggtcgctccgttgtaccc');
607
+ assert.deepStrictEqual(seq.at(7), '>ctgB:1..800');
608
+ assert.deepStrictEqual(seq.at(-1), 'ttggtcgctccgttgtaccc');
609
+ p = new Shell(`${apollo} assembly sequence ${P} -a v1 -r ctgB -s 1 -e 1`);
610
+ seq = p.stdout.split('\n');
611
+ assert.deepStrictEqual(seq.at(0), '>ctgB:1..1');
612
+ assert.deepStrictEqual(seq.at(1), 'A');
613
+ p = new Shell(`${apollo} assembly sequence ${P} -a v1 -r ctgB -s 2 -e 4`);
614
+ seq = p.stdout.split('\n');
615
+ assert.deepStrictEqual(seq.at(0), '>ctgB:2..4');
616
+ assert.deepStrictEqual(seq.at(1), 'CAT');
617
+ p = new Shell(`${apollo} assembly sequence ${P} -r ctgB`, false);
618
+ assert.ok(p.returncode != 0);
619
+ assert.ok(p.stderr.includes('found in more than one'));
620
+ });
621
+ void globalThis.itName('Get feature by id', () => {
622
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a v1 -f`);
623
+ let p = new Shell(`${apollo} feature get ${P} -a v1`);
624
+ const ff = JSON.parse(p.stdout);
625
+ const x1 = ff.at(0)._id;
626
+ const x2 = ff.at(1)._id;
627
+ p = new Shell(`${apollo} feature get-id ${P} -i ${x1} ${x1} ${x2}`);
628
+ let out = JSON.parse(p.stdout);
629
+ assert.strictEqual(out.length, 2);
630
+ assert.deepStrictEqual(out.at(0)._id, x1);
631
+ assert.deepStrictEqual(out.at(1)._id, x2);
632
+ p = new Shell(`${apollo} feature get-id ${P} -i FOOBAR`);
633
+ assert.deepStrictEqual(p.stdout.trim(), '[]');
634
+ p = new Shell(`echo -e '${x1} \n ${x2}' | ${apollo} feature get-id ${P}`);
635
+ out = JSON.parse(p.stdout);
636
+ assert.strictEqual(out.length, 2);
637
+ });
638
+ void globalThis.itName('Assembly checks', () => {
639
+ // TODO: Improve tests once more checks exist (currently there is only
640
+ // CDSCheck)
641
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a v1 -f`);
642
+ // Test view available check type
643
+ let p = new Shell(`${apollo} assembly check ${P}`);
644
+ let out = JSON.parse(p.stdout);
645
+ assert.ok(p.stdout.includes('CDSCheck'));
646
+ const cdsCheckId = out.filter((x) => x.name === 'CDSCheck').at(0)._id;
647
+ // Test view checks set for assembly
648
+ p = new Shell(`${apollo} assembly check ${P} -a v1`);
649
+ out = JSON.parse(p.stdout);
650
+ assert.strictEqual(out.length, 1);
651
+ // Test non-existant assembly
652
+ p = new Shell(`${apollo} assembly check ${P} -a non-existant`, false);
653
+ assert.strictEqual(p.returncode, 1);
654
+ assert.ok(p.stderr.includes('non-existant'));
655
+ // Test non-existant check
656
+ p = new Shell(`${apollo} assembly check ${P} -a v1 -c not-a-check`, false);
657
+ assert.strictEqual(p.returncode, 1);
658
+ assert.ok(p.stderr.includes('not-a-check'));
659
+ // Test add checks. Test check is added as opposed to replacing current
660
+ // checks with input list
661
+ new Shell(`${apollo} assembly check ${P} -a v1 -c CDSCheck CDSCheck`);
662
+ p = new Shell(`${apollo} assembly check ${P} -a v1`);
663
+ out = JSON.parse(p.stdout);
664
+ assert.strictEqual(out.length, 1);
665
+ assert.deepStrictEqual(out.at(0).name, 'CDSCheck');
666
+ // Works also with check id
667
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a v2 -f`);
668
+ new Shell(`${apollo} assembly check ${P} -a v2 -c ${cdsCheckId}`);
669
+ p = new Shell(`${apollo} assembly check ${P} -a v2`);
670
+ out = JSON.parse(p.stdout);
671
+ assert.strictEqual(out.length, 1);
672
+ assert.deepStrictEqual(out.at(0).name, 'CDSCheck');
673
+ // Delete check
674
+ new Shell(`${apollo} assembly check ${P} -a v1 -d -c CDSCheck`);
675
+ p = new Shell(`${apollo} assembly check ${P} -a v1`);
676
+ out = JSON.parse(p.stdout);
677
+ assert.deepStrictEqual(p.stdout.trim(), '[]');
678
+ });
679
+ void globalThis.itName('Feature checks', () => {
680
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a v1 -f`);
681
+ new Shell(`${apollo} assembly check ${P} -a v1 -c CDSCheck`);
682
+ let p = new Shell(`${apollo} feature check ${P} -a v1`);
683
+ const out = JSON.parse(p.stdout);
684
+ assert.ok(out.length > 1);
685
+ assert.ok(p.stdout.includes('InternalStopCodonCheck'));
686
+ // Ids with checks
687
+ const ids = out.map((x) => x.ids);
688
+ assert.ok(new Set(ids).size > 1);
689
+ // Retrieve by feature id
690
+ const xid = [...ids].join(' ');
691
+ p = new Shell(`${apollo} feature check ${P} -i ${xid}`);
692
+ assert.ok(p.stdout.includes('InternalStopCodonCheck'));
693
+ });
694
+ void globalThis.itName('Feature checks indexed', () => {
695
+ new Shell(`${apollo} assembly add-from-fasta ${P} -a v1 test_data/tiny.fasta.gz -f`);
696
+ new Shell(`${apollo} assembly check ${P} -a v1 -c CDSCheck`);
697
+ new Shell(`${apollo} feature import ${P} -a v1 test_data/tiny.fasta.gff3 -d`);
698
+ let p = new Shell(`${apollo} feature check ${P} -a v1`);
699
+ const out = JSON.parse(p.stdout);
700
+ assert.ok(out.length > 1);
701
+ assert.ok(p.stdout.includes('InternalStopCodonCheck'));
702
+ // Ids with checks
703
+ const ids = out.map((x) => x.ids);
704
+ assert.ok(new Set(ids).size > 1);
705
+ // Retrieve by feature id
706
+ const xid = [...ids].join(' ');
707
+ p = new Shell(`${apollo} feature check ${P} -i ${xid}`);
708
+ assert.ok(p.stdout.includes('InternalStopCodonCheck'));
709
+ });
710
+ void globalThis.itName('User', () => {
711
+ let p = new Shell(`${apollo} user get ${P}`);
712
+ let out = JSON.parse(p.stdout);
713
+ assert.ok(out.length > 0);
714
+ p = new Shell(`${apollo} user get ${P} -r admin`);
715
+ const out2 = JSON.parse(p.stdout);
716
+ assert.ok(out.length > 0);
717
+ assert.ok(out.length > out2.length);
718
+ p = new Shell(`${apollo} user get ${P} -r admin -u root`);
719
+ out = JSON.parse(p.stdout);
720
+ assert.strictEqual(out.length, 1);
721
+ p = new Shell(`${apollo} user get ${P} -r readOnly -u root`);
722
+ out = JSON.parse(p.stdout);
723
+ assert.strictEqual(out.length, 0);
724
+ });
725
+ void globalThis.itName('Apollo profile env', () => {
726
+ const p = new Shell(`export APOLLO_PROFILE=testAdmin2
727
+ ${apollo} config address http://localhost:3999
728
+ ${apollo} config accessType root
729
+ ${apollo} config rootPassword pass
730
+ ${apollo} login -f
731
+ ${apollo} status
732
+ ${apollo} user get`);
733
+ assert.ok(p.stdout.includes('testAdmin2: Logged in'));
734
+ assert.ok(p.stdout.includes('createdAt'));
735
+ });
736
+ void globalThis.itName('Apollo config create env', () => {
737
+ let p = new Shell(`\
738
+ export APOLLO_DISABLE_CONFIG_CREATE=1
739
+ rm -f tmp.yml
740
+ ${apollo} config --config-file tmp.yml address http://localhost:3999`, false);
741
+ assert.ok(p.returncode != 0);
742
+ assert.ok(p.stderr.includes('does not exist yet'));
743
+ assert.ok(!fs.existsSync('tmp.yml'));
744
+ p = new Shell(`\
745
+ export APOLLO_DISABLE_CONFIG_CREATE=0
746
+ rm -f tmp.yml
747
+ ${apollo} config --config-file tmp.yml address http://localhost:3999`);
748
+ assert.strictEqual(0, p.returncode);
749
+ assert.ok(fs.existsSync('tmp.yml'));
750
+ p = new Shell(`\
751
+ unset APOLLO_DISABLE_CONFIG_CREATE
752
+ rm -f tmp.yml
753
+ ${apollo} config --config-file tmp.yml address http://localhost:3999`);
754
+ assert.strictEqual(0, p.returncode);
755
+ assert.ok(fs.existsSync('tmp.yml'));
756
+ fs.unlinkSync('tmp.yml');
757
+ });
758
+ void globalThis.itName('Invalid access', () => {
759
+ const p = new Shell(`${apollo} user get --profile foo`, false);
760
+ assert.strictEqual(1, p.returncode);
761
+ assert.ok(p.stderr.includes('Profile "foo" does not exist'));
762
+ });
763
+ void globalThis.itName('Refname alias configuration', () => {
764
+ new Shell(`${apollo} assembly add-from-gff ${P} test_data/tiny.fasta.gff3 -a asm1 -f`);
765
+ let p = new Shell(`${apollo} assembly get ${P} -a asm1`);
766
+ assert.ok(p.stdout.includes('asm1'));
767
+ assert.ok(p.stdout.includes('asm2') == false);
768
+ const asm_id = JSON.parse(p.stdout)[0]._id;
769
+ p = new Shell(`${apollo} refseq add-alias ${P} test_data/alias.txt -a asm2`, false);
770
+ assert.ok(p.stderr.includes('Assembly asm2 not found'));
771
+ p = new Shell(`${apollo} refseq add-alias ${P} test_data/alias.txt -a asm1`, false);
772
+ assert.ok(p.stdout.includes('Reference name aliases added successfully to assembly asm1'));
773
+ p = new Shell(`${apollo} refseq get ${P}`);
774
+ const refseq = JSON.parse(p.stdout.trim());
775
+ const vv1ref = refseq.filter((x) => x.assembly === asm_id);
776
+ const refname_aliases = {};
777
+ for (const x of vv1ref) {
778
+ refname_aliases[x.name] = x.aliases;
779
+ }
780
+ assert.deepStrictEqual(JSON.stringify(refname_aliases.ctgA.sort()), JSON.stringify(['ctga', 'CTGA'].sort()));
781
+ assert.deepStrictEqual(JSON.stringify(refname_aliases.ctgB.sort()), JSON.stringify(['ctgb', 'CTGB'].sort()));
782
+ assert.deepStrictEqual(JSON.stringify(refname_aliases.ctgC.sort()), JSON.stringify(['ctgc', 'CTGC'].sort()));
783
+ });
784
+ // Works locally but fails on github
785
+ void globalThis.itName('Login', () => {
786
+ // This should wait for user's input
787
+ const p = new Shell(`${apollo} login ${P}`, false, 5000);
788
+ assert.ok(p.returncode != 0);
789
+ // This should be ok
790
+ new Shell(`${apollo} login ${P} --force`, true, 5000);
791
+ });
792
+ void globalThis.itName('File upload', () => {
793
+ let p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta`);
794
+ let out = JSON.parse(p.stdout);
795
+ assert.deepStrictEqual(out.type, 'text/x-fasta');
796
+ assert.ok(out._id);
797
+ p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta`);
798
+ out = JSON.parse(p.stdout);
799
+ assert.deepStrictEqual(out.type, 'text/x-fasta');
800
+ p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta.gff3`);
801
+ out = JSON.parse(p.stdout);
802
+ assert.deepStrictEqual(out.type, 'text/x-gff3');
803
+ p = new Shell(`${apollo} file upload ${P} test_data/guest.yaml`, false);
804
+ assert.ok(p.returncode != 0);
805
+ p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta.gz`, false);
806
+ assert.ok(p.stderr.includes('it may be gzip or bgzip compressed'));
807
+ assert.ok(p.returncode != 0);
808
+ });
809
+ void globalThis.itName('File upload gzip', () => {
810
+ // Uploading a gzip file must skip compression and just copy the file
811
+ const gz = fs.readFileSync('test_data/tiny.fasta.gz');
812
+ const md5 = crypto.createHash('md5').update(gz).digest('hex');
813
+ const p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta.gz -t text/x-fasta`);
814
+ const out = JSON.parse(p.stdout);
815
+ assert.strictEqual(out.checksum, md5);
816
+ new Shell(`${apollo} assembly add-from-fasta ${P} -e -f ${out._id}`);
817
+ });
818
+ void globalThis.itName('Add assembly gzip', () => {
819
+ // Autodetect format
820
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta.gz -e -f -a vv1`);
821
+ let p = new Shell(`${apollo} assembly sequence ${P} -a vv1`);
822
+ assert.ok(p.stdout.startsWith('>'));
823
+ assert.ok(p.stdout.includes('cattgttgcggagttgaaca'));
824
+ // Skip autodetect
825
+ fs.copyFileSync('test_data/tiny.fasta', 'test_data/tmp.gz');
826
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tmp.gz -e -f -a vv1 --decompressed`);
827
+ p = new Shell(`${apollo} assembly sequence ${P} -a vv1`);
828
+ assert.ok(p.stdout.startsWith('>'));
829
+ assert.ok(p.stdout.includes('cattgttgcggagttgaaca'));
830
+ fs.unlinkSync('test_data/tmp.gz');
831
+ fs.copyFileSync('test_data/tiny.fasta.gz', 'test_data/fasta.tmp');
832
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/fasta.tmp -e -f -a vv1 --gzip`);
833
+ p = new Shell(`${apollo} assembly sequence ${P} -a vv1`);
834
+ assert.ok(p.stdout.startsWith('>'));
835
+ assert.ok(p.stdout.includes('cattgttgcggagttgaaca'));
836
+ // Autodetect false positive
837
+ p = new Shell(`${apollo} assembly add-from-fasta ${P} test_data/fasta.tmp -e -f -a vv1`, false);
838
+ assert.ok(p.returncode != 0);
839
+ fs.unlinkSync('test_data/fasta.tmp');
840
+ });
841
+ void globalThis.itName('Add editable assembly', () => {
842
+ // It would be good to check that really there was no sequence loading
843
+ new Shell(`${apollo} assembly add-from-fasta ${P} -f test_data/tiny.fasta.gz`);
844
+ let p = new Shell(`${apollo} assembly sequence ${P} -a tiny.fasta.gz`);
845
+ assert.ok(p.stdout.startsWith('>'));
846
+ assert.ok(p.stdout.includes('cattgttgcggagttgaaca'));
847
+ p = new Shell(`${apollo} assembly add-from-fasta ${P} -f test_data/tiny.fasta`, false);
848
+ assert.ok(p.returncode != 0);
849
+ assert.ok(p.stderr.includes('unless option -e/--editable is set'));
850
+ // Setting --gzi & --fai
851
+ new Shell(`${apollo} assembly add-from-fasta ${P} -f test_data/tiny2.fasta.gz --gzi test_data/tiny.fasta.gz.gzi --fai test_data/tiny.fasta.gz.fai`);
852
+ p = new Shell(`${apollo} assembly sequence ${P} -a tiny2.fasta.gz`);
853
+ assert.ok(p.stdout.startsWith('>'));
854
+ assert.ok(p.stdout.includes('cattgttgcggagttgaaca'));
855
+ });
856
+ void globalThis.itName('Add assembly from file ids not editable', () => {
857
+ // Upload and get Ids for: bgzip fasta, fai and gzi
858
+ let p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta.gz -t application/x-bgzip-fasta`);
859
+ const fastaId = JSON.parse(p.stdout)._id;
860
+ p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta.gz.fai`);
861
+ const faiId = JSON.parse(p.stdout)._id;
862
+ p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta.gz.gzi`);
863
+ const gziId = JSON.parse(p.stdout)._id;
864
+ new Shell(`${apollo} assembly add-from-fasta ${P} -f ${fastaId} --fai test_data/tiny.fasta.gz.fai --gzi test_data/tiny.fasta.gz.gzi`);
865
+ p = new Shell(`${apollo} assembly sequence ${P} -a ${fastaId}`);
866
+ assert.ok(p.stdout.startsWith('>'));
867
+ assert.ok(p.stdout.includes('cattgttgcggagttgaaca'));
868
+ new Shell(`${apollo} assembly add-from-fasta ${P} -f ${fastaId} --fai ${faiId} --gzi ${gziId}`);
869
+ p = new Shell(`${apollo} assembly sequence ${P} -a ${fastaId}`);
870
+ assert.ok(p.stdout.startsWith('>'));
871
+ });
872
+ void globalThis.itName('Add assembly from file id', () => {
873
+ let p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta`);
874
+ const fid = JSON.parse(p.stdout)._id;
875
+ p = new Shell(`${apollo} assembly add-from-fasta ${P} ${fid} -a up -e -f`);
876
+ const out = JSON.parse(p.stdout);
877
+ assert.deepStrictEqual(out.name, 'up');
878
+ assert.deepStrictEqual(out.fileIds.fa, fid);
879
+ });
880
+ void globalThis.itName('Get files', () => {
881
+ new Shell(`${apollo} file upload ${P} test_data/tiny.fasta`);
882
+ let p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta`);
883
+ const fid = JSON.parse(p.stdout)._id;
884
+ p = new Shell(`${apollo} file get ${P}`);
885
+ let out = JSON.parse(p.stdout);
886
+ assert.ok(out.length >= 2);
887
+ assert.ok(out.filter((x) => x._id === fid));
888
+ p = new Shell(`${apollo} file get ${P} -i ${fid} ${fid}`);
889
+ out = JSON.parse(p.stdout);
890
+ assert.strictEqual(out.length, 1);
891
+ p = new Shell(`${apollo} file get ${P} -i nonexists`);
892
+ out = JSON.parse(p.stdout);
893
+ assert.strictEqual(out.length, 0);
894
+ });
895
+ void globalThis.itName('Download file', () => {
896
+ let p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta`);
897
+ const up = JSON.parse(p.stdout);
898
+ if (fs.existsSync(up.basename)) {
899
+ throw new Error(`File ${up.basename} exists - if safe to do so, delete it before running this test`);
900
+ }
901
+ new Shell(`${apollo} file download ${P} -i ${up._id}`);
902
+ let down = fs.readFileSync(up.basename).toString();
903
+ assert.ok(down.startsWith('>'));
904
+ assert.ok(down.trim().endsWith('accc'));
905
+ fs.unlinkSync(up.basename);
906
+ new Shell(`${apollo} file download ${P} -i ${up._id} -o tmp.fa`);
907
+ down = fs.readFileSync('tmp.fa').toString();
908
+ assert.ok(down.startsWith('>'));
909
+ assert.ok(down.trim().endsWith('accc'));
910
+ fs.unlinkSync('tmp.fa');
911
+ p = new Shell(`${apollo} file download ${P} -i ${up._id} -o -`);
912
+ assert.ok(p.stdout.startsWith('>'));
913
+ assert.ok(p.stdout.trim().endsWith('accc'));
914
+ });
915
+ void globalThis.itName('Delete file', () => {
916
+ let p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta`);
917
+ const up1 = JSON.parse(p.stdout);
918
+ p = new Shell(`${apollo} file upload ${P} test_data/tiny.fasta`);
919
+ const up2 = JSON.parse(p.stdout);
920
+ p = new Shell(`${apollo} file delete ${P} -i ${up1._id} ${up2._id}`);
921
+ let out = JSON.parse(p.stdout);
922
+ assert.strictEqual(out.length, 2);
923
+ p = new Shell(`${apollo} file get ${P} -i ${up1._id} ${up2._id}`);
924
+ out = JSON.parse(p.stdout);
925
+ assert.strictEqual(out.length, 0);
926
+ });
927
+ void globalThis.itName('Export gff3 from editable assembly', () => {
928
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta.gz -a vv1 -f --editable`);
929
+ new Shell(`${apollo} feature import ${P} test_data/tiny.fasta.gff3 -a vv1`);
930
+ let p = new Shell(`${apollo} export gff3 ${P} vv1 --include-fasta`);
931
+ let gff = p.stdout;
932
+ assert.ok(gff.startsWith('##gff-version 3'));
933
+ assert.ok(gff.includes('multivalue=val1,val2,val3'));
934
+ assert.ok(gff.includes('##FASTA\n'));
935
+ assert.deepStrictEqual(gff.slice(-6, gff.length), 'taccc\n');
936
+ p = new Shell(`${apollo} export gff3 ${P} vv1`);
937
+ gff = p.stdout;
938
+ assert.ok(gff.startsWith('##gff-version 3'));
939
+ assert.ok(gff.includes('multivalue=val1,val2,val3'));
940
+ assert.ok(!gff.includes('##FASTA\n'));
941
+ // Invalid assembly
942
+ p = new Shell(`${apollo} export gff3 ${P} foobar`, false);
943
+ assert.ok(p.returncode != 0);
944
+ assert.ok(p.stderr.includes('foobar'));
945
+ });
946
+ void globalThis.itName('Export gff3 from non-editable assembly', () => {
947
+ new Shell(`${apollo} assembly add-from-fasta ${P} test_data/tiny.fasta.gz -a vv1 -f`);
948
+ new Shell(`${apollo} feature import ${P} test_data/tiny.fasta.gff3 -a vv1`);
949
+ let p = new Shell(`${apollo} export gff3 ${P} vv1 --include-fasta`);
950
+ let gff = p.stdout;
951
+ assert.ok(gff.startsWith('##gff-version 3'));
952
+ assert.ok(gff.includes('multivalue=val1,val2,val3'));
953
+ assert.ok(gff.includes('##FASTA\n'));
954
+ // We end with two newlines because the test data does have an extra newline at the end.
955
+ assert.deepStrictEqual(gff.slice(-7, gff.length), 'taccc\n\n');
956
+ p = new Shell(`${apollo} export gff3 ${P} vv1`);
957
+ gff = p.stdout;
958
+ assert.ok(gff.startsWith('##gff-version 3'));
959
+ assert.ok(gff.includes('multivalue=val1,val2,val3'));
960
+ assert.ok(!gff.includes('##FASTA\n'));
961
+ });
962
+ void globalThis.itName('Export gff3 from external assembly', () => {
963
+ new Shell(`${apollo} assembly add-from-fasta ${P} https://raw.githubusercontent.com/GMOD/Apollo3/refs/heads/main/packages/apollo-cli/test_data/tiny.fasta.gz -a vv1 -f`);
964
+ new Shell(`${apollo} feature import ${P} test_data/tiny.fasta.gff3 -a vv1`);
965
+ let p = new Shell(`${apollo} export gff3 ${P} vv1 --include-fasta`);
966
+ let gff = p.stdout;
967
+ assert.ok(gff.startsWith('##gff-version 3'));
968
+ assert.ok(gff.includes('multivalue=val1,val2,val3'));
969
+ assert.ok(gff.includes('##FASTA\n'));
970
+ // We end with two newlines because the test data does have an extra newline at the end.
971
+ assert.deepStrictEqual(gff.slice(-7, gff.length), 'taccc\n\n');
972
+ p = new Shell(`${apollo} export gff3 ${P} vv1`);
973
+ gff = p.stdout;
974
+ assert.ok(gff.startsWith('##gff-version 3'));
975
+ assert.ok(gff.includes('multivalue=val1,val2,val3'));
976
+ assert.ok(!gff.includes('##FASTA\n'));
977
+ });
978
+ });