@@ -1277,43 +1277,242 @@ def test_replay_supplement_zipfile(self):
12771277
12781278 self .assertEqual (os .listdir (unzipped_path ), ['supplement' ])
12791279
1280- # TODO: this is a super minimal test just to confirm these commands all
1281- # run without failing. After 2025.4 release, I will add a more complete
1282- # test suite to assert that the outputs of each of these commands are
1283- # what we expect them to be
1280+
1281+ class TestAnnotations (unittest .TestCase ):
1282+ def setUp (self ):
1283+ get_dummy_plugin ()
1284+ self .runner = CliRunner ()
1285+ self .tempdir = tempfile .mkdtemp (prefix = 'qiime2-q2cli-test-temp-' )
1286+
1287+ self .art1 = os .path .join (self .tempdir , 'ints1.qza' )
1288+ Artifact .import_data ('IntSequence1' , [0 , 1 , 2 ]).save (self .art1 )
1289+ self .output1 = os .path .join (self .tempdir , 'ints1_annotated.qza' )
1290+
1291+ self .note_file = os .path .join (
1292+ self .tempdir , 'note_file.txt' )
1293+
1294+ with open (self .note_file , 'w' ) as f :
1295+ f .write ('my special text' )
1296+
1297+ def tearDown (self ):
1298+ shutil .rmtree (self .tempdir )
1299+
12841300 def test_annotation_commands_roundtrip (self ):
1285- with tempfile .TemporaryDirectory () as tempdir :
1286- in_fp = os .path .join (self .tempdir , 'concated_ints.qza' )
1287- out_fp = os .path .join (tempdir , 'concated_ints_roundtrip.qza' )
1301+ create_result = self .runner .invoke (
1302+ tools ,
1303+ ['annotation-create' , '--input-path' , self .art1 ,
1304+ '--annotation-type' , 'Note' ,
1305+ '--name' , 'mynote' , '--text' , 'my special text' ,
1306+ '--output-path' , self .output1 ]
1307+ )
1308+ self .assertEqual (create_result .exit_code , 0 )
12881309
1289- create_result = self .runner .invoke (
1290- tools ,
1291- ['annotation-create' , '--input-path' , in_fp ,
1292- '--annotation-type' , 'Note' ,
1293- '--name' , 'mynote' , '--text' , 'my special text' ,
1294- '--output-path' , out_fp ]
1295- )
1296- self .assertEqual (create_result .exit_code , 0 )
1310+ fetch_result = self .runner .invoke (
1311+ tools ,
1312+ ['annotation-fetch' , '--input-path' , self .output1 ,
1313+ '--name' , 'mynote' ]
1314+ )
1315+ self .assertEqual (fetch_result .exit_code , 0 )
12971316
1298- fetch_result = self .runner .invoke (
1299- tools ,
1300- ['annotation-fetch' , '--input-path' , out_fp ,
1301- '--name' , 'mynote' ]
1302- )
1303- self .assertEqual (fetch_result .exit_code , 0 )
1317+ list_result = self .runner .invoke (
1318+ tools ,
1319+ ['annotation-list' , '--input-path' , self .output1 ]
1320+ )
1321+ self .assertEqual (list_result .exit_code , 0 )
13041322
1305- list_result = self .runner .invoke (
1306- tools ,
1307- ['annotation-list' , '--input-path' , out_fp ]
1308- )
1309- self .assertEqual (list_result .exit_code , 0 )
1323+ remove_result = self .runner .invoke (
1324+ tools ,
1325+ ['annotation-remove' , '--input-path' , self .output1 ,
1326+ '--name' , 'mynote' , '--output-path' , self .output1 ]
1327+ )
1328+ self .assertEqual (remove_result .exit_code , 0 )
13101329
1311- remove_result = self .runner .invoke (
1312- tools ,
1313- ['annotation-remove' , '--input-path' , out_fp ,
1314- '--name' , 'mynote' , '--output-path' , out_fp ]
1315- )
1316- self .assertEqual (remove_result .exit_code , 0 )
1330+ def test_annotation_create_with_text (self ):
1331+ create_result = self .runner .invoke (
1332+ tools ,
1333+ ['annotation-create' , '--input-path' , self .art1 ,
1334+ '--annotation-type' , 'Note' ,
1335+ '--name' , 'mynote' , '--text' , 'my special text' ,
1336+ '--output-path' , self .output1 ]
1337+ )
1338+ # confirm the command doesn't produce an error
1339+ self .assertEqual (create_result .exit_code , 0 )
1340+
1341+ output_uuid = Result .load (self .output1 ).uuid
1342+ # this will just produce one annotation
1343+ for annotation in Result .load (self .output1 ).iter_annotations ():
1344+ annotation_uuid = annotation .id
1345+
1346+ exp_namelist = {
1347+ f'{ output_uuid } /checksums.sha512' ,
1348+ f'{ output_uuid } /metadata.yaml' ,
1349+ f'{ output_uuid } /VERSION' ,
1350+ f'{ output_uuid } /annotations/{ annotation_uuid } /checksums.sha512' ,
1351+ f'{ output_uuid } /annotations/{ annotation_uuid } /metadata.yaml' ,
1352+ f'{ output_uuid } /annotations/{ annotation_uuid } /note.txt' ,
1353+ f'{ output_uuid } /provenance/metadata.yaml' ,
1354+ f'{ output_uuid } /provenance/citations.bib' ,
1355+ f'{ output_uuid } /provenance/VERSION' ,
1356+ f'{ output_uuid } /provenance/conda-env.yaml' ,
1357+ f'{ output_uuid } /provenance/action/action.yaml' ,
1358+ f'{ output_uuid } /data/ints.txt'
1359+ }
1360+
1361+ exp_contents = 'my special text'
1362+
1363+ with zipfile .ZipFile (self .output1 , 'r' ) as zfh :
1364+ # confirm file structure is what we expect
1365+ self .assertEqual (exp_namelist , set (zfh .namelist ()))
1366+
1367+ annotation = zfh .read (
1368+ f'{ output_uuid } /annotations/{ annotation_uuid } /note.txt'
1369+ ).decode ('utf-8' )
1370+
1371+ # confirm the annotation file contains the text we expect
1372+ self .assertEqual (exp_contents , annotation )
1373+
1374+ # make sure we see same results as w/inline text for input
1375+ def test_annotation_create_with_file (self ):
1376+ create_result = self .runner .invoke (
1377+ tools ,
1378+ ['annotation-create' , '--input-path' , self .art1 ,
1379+ '--annotation-type' , 'Note' ,
1380+ '--name' , 'mynote' , '--file' , self .note_file ,
1381+ '--output-path' , self .output1 ]
1382+ )
1383+ # confirm the command doesn't produce an error
1384+ self .assertEqual (create_result .exit_code , 0 )
1385+
1386+ output_uuid = Result .load (self .output1 ).uuid
1387+ # this will just produce one annotation
1388+ for annotation in Result .load (self .output1 ).iter_annotations ():
1389+ annotation_uuid = annotation .id
1390+
1391+ exp_namelist = {
1392+ f'{ output_uuid } /checksums.sha512' ,
1393+ f'{ output_uuid } /metadata.yaml' ,
1394+ f'{ output_uuid } /VERSION' ,
1395+ f'{ output_uuid } /annotations/{ annotation_uuid } /checksums.sha512' ,
1396+ f'{ output_uuid } /annotations/{ annotation_uuid } /metadata.yaml' ,
1397+ f'{ output_uuid } /annotations/{ annotation_uuid } /note.txt' ,
1398+ f'{ output_uuid } /provenance/metadata.yaml' ,
1399+ f'{ output_uuid } /provenance/citations.bib' ,
1400+ f'{ output_uuid } /provenance/VERSION' ,
1401+ f'{ output_uuid } /provenance/conda-env.yaml' ,
1402+ f'{ output_uuid } /provenance/action/action.yaml' ,
1403+ f'{ output_uuid } /data/ints.txt'
1404+ }
1405+
1406+ exp_contents = 'my special text'
1407+
1408+ with zipfile .ZipFile (self .output1 , 'r' ) as zfh :
1409+ # confirm file structure is what we expect
1410+ self .assertEqual (exp_namelist , set (zfh .namelist ()))
1411+
1412+ annotation = zfh .read (
1413+ f'{ output_uuid } /annotations/{ annotation_uuid } /note.txt'
1414+ ).decode ('utf-8' )
1415+
1416+ # confirm the annotation file contains the text we expect
1417+ self .assertEqual (exp_contents , annotation )
1418+
1419+ # make sure we don't run into any errors & see same result
1420+ # when overwriting the original artifact fp
1421+ def test_annotation_create_overwriting_input_file (self ):
1422+ create_result = self .runner .invoke (
1423+ tools ,
1424+ ['annotation-create' , '--input-path' , self .art1 ,
1425+ '--annotation-type' , 'Note' ,
1426+ '--name' , 'mynote' , '--text' , 'my special text' ,
1427+ '--output-path' , self .art1 ]
1428+ )
1429+ # confirm the command doesn't produce an error
1430+ self .assertEqual (create_result .exit_code , 0 )
1431+
1432+ output_uuid = Result .load (self .art1 ).uuid
1433+ # this will just produce one annotation
1434+ for annotation in Result .load (self .art1 ).iter_annotations ():
1435+ annotation_uuid = annotation .id
1436+
1437+ exp_namelist = {
1438+ f'{ output_uuid } /checksums.sha512' ,
1439+ f'{ output_uuid } /metadata.yaml' ,
1440+ f'{ output_uuid } /VERSION' ,
1441+ f'{ output_uuid } /annotations/{ annotation_uuid } /checksums.sha512' ,
1442+ f'{ output_uuid } /annotations/{ annotation_uuid } /metadata.yaml' ,
1443+ f'{ output_uuid } /annotations/{ annotation_uuid } /note.txt' ,
1444+ f'{ output_uuid } /provenance/metadata.yaml' ,
1445+ f'{ output_uuid } /provenance/citations.bib' ,
1446+ f'{ output_uuid } /provenance/VERSION' ,
1447+ f'{ output_uuid } /provenance/conda-env.yaml' ,
1448+ f'{ output_uuid } /provenance/action/action.yaml' ,
1449+ f'{ output_uuid } /data/ints.txt'
1450+ }
1451+
1452+ exp_contents = 'my special text'
1453+
1454+ with zipfile .ZipFile (self .art1 , 'r' ) as zfh :
1455+ # confirm file structure is what we expect
1456+ self .assertEqual (exp_namelist , set (zfh .namelist ()))
1457+
1458+ annotation = zfh .read (
1459+ f'{ output_uuid } /annotations/{ annotation_uuid } /note.txt'
1460+ ).decode ('utf-8' )
1461+
1462+ # confirm the annotation file contains the text we expect
1463+ self .assertEqual (exp_contents , annotation )
1464+
1465+ def test_annotation_create_invalid_annotation_type (self ):
1466+ create_result = self .runner .invoke (
1467+ tools ,
1468+ ['annotation-create' , '--input-path' , self .art1 ,
1469+ '--annotation-type' , 'Foo' ,
1470+ '--name' , 'mynote' , '--text' , 'my special text' ,
1471+ '--output-path' , self .output1 ]
1472+ )
1473+ # confirm the command does produce an error
1474+ self .assertEqual (create_result .exit_code , 1 )
1475+ self .assertIn ("Invalid value for '--annotation-type': 'Foo'" ,
1476+ create_result .output )
1477+
1478+ def test_annotation_create_text_and_filepath_provided (self ):
1479+ create_result = self .runner .invoke (
1480+ tools ,
1481+ ['annotation-create' , '--input-path' , self .art1 ,
1482+ '--annotation-type' , 'Note' ,
1483+ '--name' , 'mynote' , '--text' , 'my special text' ,
1484+ '--file' , self .note_file , '--output-path' , self .output1 ]
1485+ )
1486+ # confirm the command does produce an error
1487+ self .assertEqual (create_result .exit_code , 2 )
1488+ self .assertIn ("Exactly one of `--text` or `--file` must be provided" ,
1489+ create_result .output )
1490+
1491+ def test_annotation_create_no_text_or_filepath_provided (self ):
1492+ create_result = self .runner .invoke (
1493+ tools ,
1494+ ['annotation-create' , '--input-path' , self .art1 ,
1495+ '--annotation-type' , 'Note' ,
1496+ '--name' , 'mynote' , '--output-path' , self .output1 ]
1497+ )
1498+ # confirm the command does produce an error
1499+ self .assertEqual (create_result .exit_code , 2 )
1500+ self .assertIn ("Exactly one of `--text` or `--file` must be provided" ,
1501+ create_result .output )
1502+
1503+ def test_annotation_create_invalid_name (self ):
1504+ create_result = self .runner .invoke (
1505+ tools ,
1506+ ['annotation-create' , '--input-path' , self .art1 ,
1507+ '--annotation-type' , 'Note' ,
1508+ '--name' , '@#$%^&*' , '--text' , 'my special text' ,
1509+ '--output-path' , self .output1 ]
1510+ )
1511+ # confirm the command does produce an error
1512+ self .assertEqual (create_result .exit_code , 1 )
1513+ # TODO: this exception should show up in the output same as others
1514+ self .assertIn ('Name "@#$%^&*" is not a valid Python identifier' ,
1515+ str (create_result .exception ))
13171516
13181517
13191518if __name__ == "__main__" :
0 commit comments