• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
Aucun tag

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

CLI interface to medialist (fossil mirror)


Commit MetaInfo

Révision10397c449c9099fb392148d82297641645555b5b (tree)
l'heure2022-01-25 13:09:56
Auteurmio <stigma@disr...>
Commitermio

Message de Log

move unit tests from medialist.d to unittest.d

FossilOrigin-Name: 82ff8c2665959337a4091820b7086c2ae37d41dd555dfff6bbca47320c746ba0

Change Summary

Modification

--- a/medialist.d
+++ b/medialist.d
@@ -205,7 +205,7 @@ MediaListItem ml_fetch_item(MediaList* list, size_t id, MLError* err = null)
205205 if (true == list.isOpen) {
206206 if (null !is err)
207207 *err = MLError.fileAlreadyOpen;
208-
208+
209209 return newItem;
210210 }
211211
@@ -730,7 +730,7 @@ private MLError _ml_update(MediaList* list, string[] args)
730730
731731 if (currentIndex == id) {
732732 string[] sections = line.strip().split("\t");
733-
733+
734734 /* Add missing columns if required (e.g. updating pre-0.2 file). */
735735 if (sections.length < headerPositions.length) {
736736 while (sections.length < headerPositions.length)
@@ -879,758 +879,3 @@ private int[2][6] _ml_get_header_positions(MediaList* list)
879879 return headerPositions;
880880 }
881881
882-
883-/*
884- * The rest of this file consists of unittests.
885- */
886-
887-version(unittest):
888-
889-import tap;
890-
891-public void runMediaListUnitTests()
892-{
893- ok(unittest_createNewList(), "Create a new list.");
894- ok(unittest_addNewItemNoProgressNoStatus(),
895- "Can add a new item with no progress or status.");
896- ok(unittest_addNewItemWithProgressNoStatus(),
897- "Can add a new item with progress but no status.");
898- ok(unittest_addNewItemNoProgressWithStatus(),
899- "Can add a new item with status but no progress.");
900- ok(unittest_addNewItemWithProgressWithStatus(),
901- "Can add a new item with progress and status.");
902- ok(unittest_fetchItem2(),
903- "Can fetch a singular item by ID (2).");
904- ok(unittest_checkPre02MLFileHeaderOrder(),
905- "Check header position variance and case-insensitivity.");
906- ok(unittest_fetchItems124(),
907- "Can fetch multiple items by ID (1, 4, and 2).");
908- ok(unittest_updateItemTitle(),
909- "Can update an item's title.");
910- ok(unittest_updateItemStatus(),
911- "Can update an item's status.");
912- ok(unittest_updateItemProgress(),
913- "Can update an item's progress.");
914- ok(unittest_updateItemAll(),
915- "Can update all aspects of an item.");
916-}
917-
918-private bool unittest_createNewList()
919-{
920- import std.path : buildPath;
921-
922- const listPath = buildPath(tempDir(), "unittest1.tsv");
923- scope(exit) remove(listPath);
924-
925- MediaList* list = ml_open_list(listPath);
926- scope(exit) ml_free_list(list);
927-
928- if (null is list) {
929- diag("Memory allocation failed (list is null)");
930- return false;
931- }
932-
933- if (false == exists(listPath)) {
934- diag("A new file wasn't created for the list");
935- return false;
936- }
937-
938- int[2][6] headerPositions = -1;
939-
940- headerPositions = _ml_get_header_positions(list);
941-
942- foreach(size_t idx, const ref int[2] headerPosition; headerPositions) {
943- if (headerPosition[0] == -1) {
944- diag("Empty header type: " ~ to!string(idx));
945- return false;
946- }
947- if (headerPosition[1] == -1) {
948- diag("Empty header position: " ~ to!string(idx));
949- return false;
950- }
951- }
952-
953- return true;
954-}
955-
956-private bool unittest_addNewItemNoProgressNoStatus()
957-{
958- import std.path : buildPath;
959-
960- const listPath = buildPath(tempDir(), "unittest2.tsv");
961- scope(exit) remove(listPath);
962-
963- MediaList* list = ml_open_list(listPath);
964- scope(exit) ml_free_list(list);
965-
966- MLError res = ml_send_command(list, MLCommand.add, ["Item 1"]);
967- if (res != MLError.success) {
968- diag(`Failed to send MLCommand.add ["Item 1"].`);
969- return false;
970- }
971-
972- auto f = File(listPath);
973- string line;
974- bool readHeader = false;
975- string[] sections;
976-
977- while ((line = f.readln) !is null) {
978- if (line[0] == '#')
979- continue;
980-
981- if (false == readHeader) {
982- readHeader = true;
983- continue;
984- }
985-
986- sections = line.strip().split("\t");
987- break;
988- }
989-
990- int[2][6] headerPositions = _ml_get_header_positions(list);
991-
992- if (sections.length != MLHeaders.max + 1) {
993- diag("Differing amount of headers in unit test files.");
994- return false;
995- }
996-
997- if (sections[headerPositions[MLHeaders.title][1]] != "Item 1") {
998- diag(`New item with title "Item 1" wasn't saved.`);
999- return false;
1000- }
1001- if (sections[headerPositions[MLHeaders.progress][1]] != "-/-") {
1002- diag(`New item with unspecified progress wasn't "-/-".`);
1003- return false;
1004- }
1005- if (sections[headerPositions[MLHeaders.status][1]] != "UNKNOWN") {
1006- diag(`New item with unspecified status wasn't "UNKNOWN".`);
1007- return false;
1008- }
1009-
1010- if (sections[headerPositions[MLHeaders.startDate][1]] != "") {
1011- diag(`New item does not have blank "start_date" field.`);
1012- return false;
1013- }
1014- if (sections[headerPositions[MLHeaders.endDate][1]] != "") {
1015- diag(`New item does not have blank "end_date" field.`);
1016- return false;
1017- }
1018-
1019- DateTime currentDT = cast(DateTime)Clock.currTime;
1020- string currentDS = format!"%d-%02d-%02d"(currentDT.year,
1021- currentDT.month, currentDT.day);
1022-
1023- if (sections[headerPositions[MLHeaders.lastUpdated][1]] != currentDS) {
1024- diag(`New item was not "last_updated" with today's date.`);
1025- return false;
1026- }
1027-
1028- return true;
1029-}
1030-
1031-private bool unittest_addNewItemWithProgressNoStatus()
1032-{
1033- import std.path : buildPath;
1034-
1035- const listPath = buildPath(tempDir(), "unittest3.tsv");
1036- scope(exit) remove(listPath);
1037-
1038- MediaList* list = ml_open_list(listPath);
1039- scope(exit) ml_free_list(list);
1040-
1041- MLError res = ml_send_command(list, MLCommand.add, ["Item 1", "10/-"]);
1042- if (res != MLError.success) {
1043- diag(`Failed to send MLCommand.add with ["Item 1", "10/-"].`);
1044- return false;
1045- }
1046-
1047- auto f = File(listPath);
1048- string line;
1049- bool readHeader = false;
1050- string[] sections;
1051-
1052- while ((line = f.readln) !is null) {
1053- if (line[0] == '#')
1054- continue;
1055-
1056- if (false == readHeader) {
1057- readHeader = true;
1058- continue;
1059- }
1060-
1061- sections = line.strip().split("\t");
1062- }
1063-
1064- int[2][6] headerPositions = _ml_get_header_positions(list);
1065-
1066- if (sections.length != MLHeaders.max + 1) {
1067- diag("Differing amount of headers in unit test files.");
1068- return false;
1069- }
1070-
1071- if (sections[headerPositions[MLHeaders.title][1]] != "Item 1") {
1072- diag(`New item with title "Item 1" wasn't saved.`);
1073- return false;
1074- }
1075- if (sections[headerPositions[MLHeaders.progress][1]] != "10/-") {
1076- diag(`New item with progress "10/-" wasn't saved.`);
1077- return false;
1078- }
1079- if (sections[headerPositions[MLHeaders.status][1]] != "UNKNOWN") {
1080- diag(`New item with unspecified status wasn't "UNKNOWN".`);
1081- return false;
1082- }
1083-
1084- if (sections[headerPositions[MLHeaders.startDate][1]] != "") {
1085- diag(`New item does not have blank "start_date" field.`);
1086- return false;
1087- }
1088- if (sections[headerPositions[MLHeaders.endDate][1]] != "") {
1089- diag(`New item does not have blank "end_date" field.`);
1090- return false;
1091- }
1092-
1093- DateTime currentDT = cast(DateTime)Clock.currTime;
1094- string currentDS = format!"%d-%02d-%02d"(currentDT.year,
1095- currentDT.month, currentDT.day);
1096-
1097- if (sections[headerPositions[MLHeaders.lastUpdated][1]] != currentDS) {
1098- diag(`New item was not "last_updated" with today's date.`);
1099- return false;
1100- }
1101-
1102- return true;
1103-}
1104-
1105-private bool unittest_addNewItemNoProgressWithStatus()
1106-{
1107- import std.path : buildPath;
1108-
1109- const listPath = buildPath(tempDir(), "unittest3.tsv");
1110- scope(exit) remove(listPath);
1111-
1112- MediaList* list = ml_open_list(listPath);
1113- scope(exit) ml_free_list(list);
1114-
1115- MLError res = ml_send_command(list, MLCommand.add,
1116- ["Item 1", null, "COMPLETE"]);
1117- if (res != MLError.success) {
1118- diag(`Failed to send MLCommand.add with ["Item 1", null, "COMPLETE"].`);
1119- return false;
1120- }
1121-
1122- auto f = File(listPath);
1123- string line;
1124- bool readHeader = false;
1125- string[] sections;
1126-
1127- while ((line = f.readln) !is null) {
1128- if (line[0] == '#')
1129- continue;
1130-
1131- if (false == readHeader) {
1132- readHeader = true;
1133- continue;
1134- }
1135-
1136- sections = line.strip().split("\t");
1137- }
1138-
1139- int[2][6] headerPositions = _ml_get_header_positions(list);
1140-
1141- if (sections.length != MLHeaders.max + 1) {
1142- diag("Differing amount of headers in unit test files.");
1143- return false;
1144- }
1145-
1146- if (sections[headerPositions[MLHeaders.title][1]] != "Item 1") {
1147- diag(`New item with title "Item 1" wasn't saved.`);
1148- return false;
1149- }
1150- if (sections[headerPositions[MLHeaders.progress][1]] != "-/-") {
1151- diag(`New item with unspecified progress wasn't "-/-".`);
1152- return false;
1153- }
1154- if (sections[headerPositions[MLHeaders.status][1]] != "COMPLETE") {
1155- diag(`New item with status "COMPLETE" wasn't saved.`);
1156- return false;
1157- }
1158-
1159- if (sections[headerPositions[MLHeaders.startDate][1]] != "") {
1160- diag(`New item does not have blank "start_date" field.`);
1161- return false;
1162- }
1163- if (sections[headerPositions[MLHeaders.endDate][1]] != "") {
1164- diag(`New item does not have blank "end_date" field.`);
1165- return false;
1166- }
1167-
1168- DateTime currentDT = cast(DateTime)Clock.currTime;
1169- string currentDS = format!"%d-%02d-%02d"(currentDT.year,
1170- currentDT.month, currentDT.day);
1171-
1172- if (sections[headerPositions[MLHeaders.lastUpdated][1]] != currentDS) {
1173- diag(`New item was not "last_updated" with today's date.`);
1174- return false;
1175- }
1176-
1177- return true;
1178-}
1179-
1180-private bool unittest_addNewItemWithProgressWithStatus()
1181-{
1182- import std.path : buildPath;
1183-
1184- const listPath = buildPath(tempDir(), "unittest3.tsv");
1185- scope(exit) remove(listPath);
1186-
1187- MediaList* list = ml_open_list(listPath);
1188- scope(exit) ml_free_list(list);
1189-
1190- MLError res = ml_send_command(list, MLCommand.add,
1191- ["Item 1", "10/-", "COMPLETE"]);
1192- if (res != MLError.success) {
1193- diag(`Failed to send MLCommand.add with `
1194- ~ `["Item 1", "10/-", "COMPLETE"].`);
1195- return false;
1196- }
1197-
1198- auto f = File(listPath);
1199- string line;
1200- bool readHeader = false;
1201- string[] sections;
1202-
1203- while ((line = f.readln) !is null) {
1204- if (line[0] == '#')
1205- continue;
1206-
1207- if (false == readHeader) {
1208- readHeader = true;
1209- continue;
1210- }
1211-
1212- sections = line.strip().split("\t");
1213- }
1214-
1215- int[2][6] headerPositions = _ml_get_header_positions(list);
1216-
1217- if (sections.length != MLHeaders.max + 1) {
1218- diag("Differing amount of headers in unit test files.");
1219- return false;
1220- }
1221-
1222- if (sections[headerPositions[MLHeaders.title][1]] != "Item 1") {
1223- diag(`New item with title "Item 1" wasn't saved.`);
1224- return false;
1225- }
1226- if (sections[headerPositions[MLHeaders.progress][1]] != "10/-") {
1227- diag(`New item with progress "10/-" wasn't saved.`);
1228- return false;
1229- }
1230- if (sections[headerPositions[MLHeaders.status][1]] != "COMPLETE") {
1231- diag(`New item with status "COMPLETE" wasn't saved.`);
1232- return false;
1233- }
1234-
1235- if (sections[headerPositions[MLHeaders.startDate][1]] != "") {
1236- diag(`New item does not have blank "start_date" field.`);
1237- return false;
1238- }
1239- if (sections[headerPositions[MLHeaders.endDate][1]] != "") {
1240- diag(`New item does not have blank "end_date" field.`);
1241- return false;
1242- }
1243-
1244- DateTime currentDT = cast(DateTime)Clock.currTime;
1245- string currentDS = format!"%d-%02d-%02d"(currentDT.year,
1246- currentDT.month, currentDT.day);
1247-
1248- if (sections[headerPositions[MLHeaders.lastUpdated][1]] != currentDS) {
1249- diag(`New item was not "last_updated" with today's date.`);
1250- return false;
1251- }
1252-
1253- return true;
1254-}
1255-
1256-private bool unittest_fetchItem2()
1257-{
1258- MediaList *list = ml_open_list(buildPath(getcwd(), "unittest_fetchItem2.tsv"));
1259-
1260- if (null is list)
1261- return false;
1262-
1263- scope(exit) {
1264- ml_send_command(list, MLCommand.delete_, []);
1265- ml_free_list(list);
1266- }
1267-
1268- MLError ec;
1269-
1270- ec = ml_send_command(list, MLCommand.add, ["Item 1"]);
1271- if (MLError.success != ec) {
1272- diag("Failed to send add command [Item 1].");
1273- return false;
1274- }
1275-
1276- ec = ml_send_command(list, MLCommand.add, ["Item 2", "1/-", "READING"]);
1277- if (MLError.success != ec) {
1278- diag("Failed to send add command [Item 2, 1/-, READING]");
1279- return false;
1280- }
1281-
1282- MediaListItem item2 = ml_fetch_item(list, 2, &ec);
1283- if (MLError.success != ec) {
1284- diag(`Failed to fetch item 2 (ec = ` ~ to!string(ec) ~ `)`);
1285- return false;
1286- }
1287-
1288- if ("Item 2" != item2.title) {
1289- diag(`ml_fetch_item(2).title != "Item 2". (got: ` ~ item2.title ~ ")");
1290- return false;
1291- }
1292-
1293- if ("1/-" != item2.progress) {
1294- diag(`ml_fetch_item(2).progress != "1/-". (got: ` ~ item2.progress ~ ")");
1295- return false;
1296- }
1297-
1298- if ("READING" != item2.status) {
1299- diag(`ml_fetch_item(2).status != "READING". (got: ` ~ item2.status ~ ")");
1300- return false;
1301- }
1302-
1303- return true;
1304-}
1305-
1306-/*
1307- * Emulate pre-0.2 medialist files to confirm that headers can be in any order
1308- * and that it is case-insensitive.
1309- */
1310-private bool unittest_checkPre02MLFileHeaderOrder()
1311-{
1312- enum listName = "checkPre02MLFileHeaderOrder";
1313- enum fileName = listName ~ ".tsv";
1314-
1315- /* case-insensitive */
1316- auto listFile = File(fileName, "w+");
1317- listFile.writeln("TITLE\tPROGRESS\tSTATUS");
1318- listFile.writeln("Item 1\t??/??\tUNKNOWN");
1319- listFile.close();
1320-
1321- MediaList *list = ml_open_list(fileName);
1322-
1323- /* shouldn't crash */
1324- MediaListHeader[] headers = ml_fetch_headers(list);
1325- if (headers.length != 3) {
1326- diag("headers.length != 3 (case IS sensitive)");
1327- return false;
1328- }
1329-
1330- /* shouldn't crash */
1331- MediaListItem[] items = ml_fetch_all(list);
1332-
1333- ml_send_command(list, MLCommand.delete_, []);
1334- ml_free_list(list);
1335-
1336- listFile = File(fileName, "w+");
1337- listFile.writeln ("PROGRESS\tTItLE\tstatus");
1338- listFile.writeln ("??/??\tItem 1\tUNKNOWN");
1339- listFile.close();
1340-
1341- list = ml_open_list(fileName);
1342- items = ml_fetch_all(list);
1343-
1344- headers = ml_fetch_headers(list);
1345- if (headers.length != 3) {
1346- diag("headers.length != 3 (order IS hard-coded)");
1347- return false;
1348- }
1349-
1350- ml_send_command(list, MLCommand.delete_, []);
1351- ml_free_list(list);
1352-
1353- return true;
1354-}
1355-
1356-private bool unittest_fetchItems124()
1357-{
1358- enum listName = __FUNCTION__;
1359- enum fileName = listName ~ ".tsv";
1360-
1361- MediaList* list = ml_open_list(fileName);
1362- if (null is list) {
1363- diag("list returned from ml_open_list is null.");
1364- return false;
1365- }
1366-
1367- scope (exit) {
1368- /* delete the entire list and file. */
1369- ml_send_command(list, MLCommand.delete_, []);
1370- ml_free_list(list);
1371- }
1372-
1373- MLError res;
1374-
1375- res = ml_send_command(list, MLCommand.add, ["Item 1", "PLAN-TO-READ", "-/-"]);
1376- if (MLError.success != res) {
1377- diag("Couldn't add [Item 1, PLAN-TO-READ, -/-] to list.");
1378- return false;
1379- }
1380-
1381- res = ml_send_command(list, MLCommand.add, ["Item 2", "READING", "10/12"]);
1382- if (MLError.success != res) {
1383- diag("Couldn't add [Item 2, READING, 10/12] to list.");
1384- return false;
1385- }
1386-
1387- res = ml_send_command(list, MLCommand.add, ["Item 3"]);
1388- if (MLError.success != res) {
1389- diag("Couldn't add [Item 3] to list");
1390- return false;
1391- }
1392-
1393- res = ml_send_command(list, MLCommand.add, ["Item 4", "COMPLETE", "123/123"]);
1394- if (MLError.success != res) {
1395- diag("Couldn't add [Item 4, COMPLETE, 123/123] to list.");
1396- return false;
1397- }
1398-
1399- MediaListItem[] specificItems = ml_fetch_items(list, null, 1, 4, 2);
1400- if (null is specificItems) {
1401- diag("ml_fetch_items(list, 1, 4, 2) returned null.");
1402- return false;
1403- }
1404-
1405- if (3 != specificItems.length) {
1406- diag("ml_fetch_items(list, 1, 4, 2) didn't return 3 items.");
1407- return false;
1408- }
1409-
1410- if ("Item 1" != specificItems[0].title) {
1411- diag("specificItems[0].title != Item 1.");
1412- return false;
1413- }
1414-
1415- if ("Item 2" != specificItems[1].title) {
1416- diag("specificItems[1].title != Item 2.");
1417- return false;
1418- }
1419-
1420- if ("Item 4" != specificItems[2].title) {
1421- diag("specificItems[2].title != Item 4.");
1422- return false;
1423- }
1424-
1425- return true;
1426-}
1427-
1428-private bool unittest_updateItemTitle()
1429-{
1430- enum listName = __FUNCTION__;
1431- enum fileName = listName ~ ".tsv";
1432-
1433- MediaList* list = ml_open_list(fileName);
1434- if (list is null) {
1435- diag("ml_open_list returned null.");
1436- return false;
1437- }
1438-
1439- scope(exit) {
1440- ml_send_command(list, MLCommand.delete_, []);
1441- ml_free_list(list);
1442- }
1443-
1444- MLError res;
1445-
1446- res = ml_send_command(list, MLCommand.add, ["Ietm 1"]);
1447- if (MLError.success != res) {
1448- diag("ml_send_command(list, add, [Ietm 1]) failed.");
1449- return false;
1450- }
1451-
1452- res = ml_send_command(list, MLCommand.update, ["1", "title::Item 1"]);
1453- if (MLError.success != res) {
1454- diag("Failed to send UPDATE command [1, title::Item 1].");
1455- return false;
1456- }
1457-
1458- MediaListItem item = ml_fetch_item(list, 1, &res);
1459- if (MLError.success != res) {
1460- diag("Failed to fetch item 1 from list.");
1461- return false;
1462- }
1463-
1464- if ("Item 1" != item.title) {
1465- diag("Failed to update title from 'Ietm 1' to 'Item 1'.");
1466- return false;
1467- }
1468-
1469- return true;
1470-}
1471-
1472-private bool unittest_updateItemStatus()
1473-{
1474- enum listName = __FUNCTION__;
1475- enum fileName = listName ~ ".tsv";
1476-
1477- MediaList* list = ml_open_list(fileName);
1478- if (list is null) {
1479- diag("ml_open_list returned null.");
1480- return false;
1481- }
1482-
1483- scope(exit) {
1484- ml_send_command(list, MLCommand.delete_, []);
1485- ml_free_list(list);
1486- }
1487-
1488- MLError res;
1489-
1490- res = ml_send_command(list, MLCommand.add, ["Item 1", null, "PLAN-TO-READ"]);
1491- if (MLError.success != res) {
1492- diag("ml_send_command(list, add, [Item 1, null, PLAN-TO-READ]) failed.");
1493- return false;
1494- }
1495-
1496- res = ml_send_command(list, MLCommand.update, ["1", "status::READING"]);
1497- if (MLError.success != res) {
1498- diag("Failed to send UPDATE command [1, status::READING].");
1499- return false;
1500- }
1501-
1502- MediaListItem item = ml_fetch_item(list, 1, &res);
1503- if (MLError.success != res) {
1504- diag("Failed to fetch item 1 from list.");
1505- return false;
1506- }
1507-
1508- if ("READING" != item.status) {
1509- diag("Failed to update status from 'PLAN-TO-READ' to 'READING'.");
1510- return false;
1511- }
1512-
1513- return true;
1514-}
1515-
1516-private bool unittest_updateItemProgress()
1517-{
1518- enum listName = __FUNCTION__;
1519- enum fileName = listName ~ ".tsv";
1520-
1521- MediaList* list = ml_open_list(fileName);
1522- if (list is null) {
1523- diag("ml_open_list returned null.");
1524- return false;
1525- }
1526-
1527- scope(exit) {
1528- ml_send_command(list, MLCommand.delete_, []);
1529- ml_free_list(list);
1530- }
1531-
1532- MLError res;
1533-
1534- res = ml_send_command(list, MLCommand.add, ["Item 1"]);
1535- if (MLError.success != res) {
1536- diag("ml_send_command(list, add, [Item 1]) failed.");
1537- return false;
1538- }
1539-
1540- res = ml_send_command(list, MLCommand.update, ["1", "progress::10/10"]);
1541- if (MLError.success != res) {
1542- diag("Failed to send UPDATE command [1, progress::10/10].");
1543- return false;
1544- }
1545-
1546- MediaListItem item = ml_fetch_item(list, 1);
1547- if (false == item.valid) {
1548- diag("Failed to fetch item 1 from list.");
1549- return false;
1550- }
1551-
1552- if ("10/10" != item.progress) {
1553- diag("Failed to update progress from '-/-' to '10/10'.");
1554- return false;
1555- }
1556-
1557- return true;
1558-}
1559-
1560-private bool unittest_updateItemAll()
1561-{
1562- enum listName = __FUNCTION__;
1563- enum fileName = listName ~ ".tsv";
1564-
1565- MediaList* list = ml_open_list(fileName);
1566- if (list is null) {
1567- diag("ml_open_list returned null.");
1568- return false;
1569- }
1570-
1571- scope(exit) {
1572- ml_send_command(list, MLCommand.delete_, []);
1573- ml_free_list(list);
1574- }
1575-
1576- MLError res;
1577-
1578- res = ml_send_command(list, MLCommand.add, ["Ietm 1"]);
1579- if (MLError.success != res) {
1580- diag("ml_send_command(list, add, [Ietm 1]) failed.");
1581- return false;
1582- }
1583-
1584- res = ml_send_command(list, MLCommand.update,
1585- ["1",
1586- "start_date::2021-02-16",
1587- "end_date::2022-01-20",
1588- "progress::60/100",
1589- "status::READING",
1590- "title::MediaList"]
1591- );
1592- if (MLError.success != res) {
1593- diag("Failed to send UPDATE command.");
1594- return false;
1595- }
1596-
1597- MediaListItem item = ml_fetch_item(list, 1, &res);
1598- if (MLError.success != res) {
1599- diag("Failed to fetch item 1 from list.");
1600- return false;
1601- }
1602-
1603- if ("MediaList" != item.title) {
1604- diag("Failed to update item title from 'Ietm 1' to 'MediaList'.");
1605- return false;
1606- }
1607-
1608- if ("60/100" != item.progress) {
1609- diag("Failed to update progress from '-/-' to '60/100'.");
1610- return false;
1611- }
1612-
1613- if ("READING" != item.status) {
1614- diag("Failed to update status from 'UNKNOWN' to 'READING'.");
1615- return false;
1616- }
1617-
1618- if ("2021-02-16" != item.startDate) {
1619- diag("Failed to update start_date from '' to '2021-02-16'.");
1620- return false;
1621- }
1622-
1623- if ("2022-01-20" != item.endDate) {
1624- diag("Failed to update end_date from '' to '2022-01-20'.");
1625- return false;
1626- }
1627-
1628- Date date = cast(Date)Clock.currTime;
1629- string currentDate = format!"%d-%02d-%02d"(date.year, date.month, date.day);
1630- if (currentDate != item.lastUpdated) {
1631- diag("Failed to update last_update when running the UPDATE command.");
1632- return false;
1633- }
1634-
1635- return true;
1636-}
--- a/unittests.d
+++ b/unittests.d
@@ -20,6 +20,12 @@ module unittests;
2020
2121 version(unittest):
2222
23+import std.conv;
24+import std.datetime.date;
25+import std.file;
26+import std.path;
27+import std.string;
28+
2329 import medialist;
2430 import tap;
2531
@@ -40,9 +46,7 @@ void main()
4046 {
4147 alias tests = Tuple!(__traits(getUnitTests, unittests));
4248
43- plan(TAPPlan.noPlan);
44- runMediaListUnitTests();
45-
49+ plan(tests.length);
4650
4751 foreach(i, test; tests) {
4852 alias attributes = Tuple!(__traits(getAttributes, tests[i]));
@@ -171,7 +175,7 @@ unittest
171175 assert(MLError.success == err, "Failed to send ADD command [Item 1].");
172176
173177 string currentDate = format!"%d-%02d-%02d"(date.year, date.month, date.day);
174-
178+
175179 MediaListItem item = ml_fetch_item(ml, 1, &err);
176180 assert(MLError.success == err, "Failed to FETCH item ID 1.");
177181 assert(item.lastUpdated == currentDate,
@@ -198,7 +202,7 @@ unittest
198202
199203 MediaList* ml = ml_open_list(fileName);
200204 assert(ml !is null, "ml_open_list returned null.");
201-
205+
202206 scope (exit) {
203207 ml_send_command(ml, MLCommand.delete_, []);
204208 ml_free_list(ml);
@@ -207,7 +211,7 @@ unittest
207211 File listFile = File(fileName, "a+");
208212 listFile.writeln("4d4545979a\tUNKNOWN\t??/??"); // id will be 1
209213 listFile.close();
210-
214+
211215 MLError err = ml_send_command(ml, MLCommand.update, ["1", "progress::-/-"]);
212216 assert(MLError.success == err, "Failed to send UPDATE command [1, progress::-/-].");
213217
@@ -248,3 +252,590 @@ unittest
248252 assert("" != sections[lu_index],
249253 `Missing "last_updated" value after updating pre-0.2 list.`);
250254 }
255+
256+@(`Create a new list.`)
257+unittest
258+{
259+ const listPath = buildPath(tempDir(), "unittest1.tsv");
260+ scope(exit) remove(listPath);
261+
262+ MediaList* list = ml_open_list(listPath);
263+ scope(exit) ml_free_list(list);
264+
265+ assert(null !is list, "Memory allocation failed (list is null).");
266+ assert(true == exists(listPath), "A new file wasn't created for the list.");
267+
268+ MediaListHeader[] headers = ml_fetch_headers(list, null);
269+ assert(null !is headers, "Failed to parse headers");
270+
271+ foreach(const ref header; headers) {
272+ assert(null !is header.tsvName, "Failed to find header line in mTSV file");
273+ }
274+}
275+
276+@(`Can add a new item with no progress or status.`)
277+unittest
278+{
279+ const listPath = buildPath(tempDir(), "unittest2.tsv");
280+ scope(exit) remove(listPath);
281+
282+ MediaList* list = ml_open_list(listPath);
283+ assert(null !is list, "Failed to allocate memory for list.");
284+
285+ scope(exit) ml_free_list(list);
286+
287+ MLError res = ml_send_command(list, MLCommand.add, ["Item 1"]);
288+ assert(res == MLError.success, `Failed to send MLCommand.add ["Item 1"].`);
289+
290+ auto f = File(listPath);
291+ string line;
292+ bool readHeader = false;
293+ string[] sections;
294+
295+ while ((line = f.readln) !is null) {
296+ if (line[0] == '#')
297+ continue;
298+
299+ if (false == readHeader) {
300+ readHeader = true;
301+ continue;
302+ }
303+
304+ sections = line.strip().split("\t");
305+ break;
306+ }
307+
308+ Date currentD = cast(Date)Clock.currTime;
309+ string currentDS = format!"%d-%02d-%02d"(currentD.year,
310+ currentD.month, currentD.day);
311+
312+ MediaListHeader[] headers = ml_fetch_headers(list, null);
313+ assert(null !is headers, "Failed to fetch headers from list.");
314+ assert(sections.length == headers.length,
315+ "Differing amount of headers in unit test file.");
316+
317+ foreach(const ref header; headers) {
318+ switch (header.tsvName) {
319+ case "title":
320+ assert("Item 1" == sections[header.column],
321+ `New item with title "Item 1" wasn't saved.`);
322+ break;
323+ case "progress":
324+ assert("-/-" == sections[header.column],
325+ `New item with unspecified progress wasn't "-/-".`);
326+ break;
327+ case "status":
328+ assert("UNKNOWN" == sections[header.column],
329+ `New item with unspecified status wasn't "UNKNOWN".`);
330+ break;
331+ case "start_date":
332+ assert("" == sections[header.column],
333+ `New item does not have blank "start_date" field.`);
334+ break;
335+ case "end_date":
336+ assert("" == sections[header.column],
337+ `New item does not have blank "end_date" field.`);
338+ break;
339+ case "last_updated":
340+ assert(currentDS == sections[header.column],
341+ `New item was not "last_updated" with today's date.`);
342+ break;
343+ default:
344+ assert(false, "Unknown header: " ~ header.tsvName);
345+ }
346+ }
347+}
348+
349+@(`Can add a new item with progress but no status.`)
350+unittest
351+{
352+ const listPath = buildPath(tempDir(), "unittest3.tsv");
353+ scope(exit) remove(listPath);
354+
355+ MediaList* list = ml_open_list(listPath);
356+ assert(null !is list, "Failed to allocate memory for list.");
357+ scope(exit) ml_free_list(list);
358+
359+ MLError res = ml_send_command(list, MLCommand.add, ["Item 1", "10/-"]);
360+ assert(MLError.success == res,
361+ `Failed to send MLCommand.add with ["Item 1", "10/-"].`);
362+
363+ auto f = File(listPath);
364+ string line;
365+ bool readHeader = false;
366+ string[] sections;
367+
368+ while ((line = f.readln) !is null) {
369+ if (line[0] == '#')
370+ continue;
371+
372+ if (false == readHeader) {
373+ readHeader = true;
374+ continue;
375+ }
376+
377+ sections = line.strip().split("\t");
378+ }
379+
380+ Date currentD = cast(Date)Clock.currTime;
381+ string currentDS = format!"%d-%02d-%02d"(currentD.year,
382+ currentD.month, currentD.day);
383+
384+ MediaListHeader[] headers = ml_fetch_headers(list, null);
385+ assert(null !is headers, "Failed to fetch headers from list.");
386+ assert(sections.length == headers.length,
387+ "Differing amount of headers in unit test file.");
388+
389+ foreach(const ref header; headers) {
390+ switch (header.tsvName) {
391+ case "title":
392+ assert("Item 1" == sections[header.column],
393+ `New item with title "Item 1" wasn't saved.`);
394+ break;
395+ case "progress":
396+ assert("10/-" == sections[header.column],
397+ `New item with progress "10/-" wasn't saved.`);
398+ break;
399+ case "status":
400+ assert("UNKNOWN" == sections[header.column],
401+ `New item with unspecified status wasn't "UNKNOWN".`);
402+ break;
403+ case "start_date":
404+ assert("" == sections[header.column],
405+ `New item does not have blank "start_date" field.`);
406+ break;
407+ case "end_date":
408+ assert("" == sections[header.column],
409+ `New item does not have blank "end_date" field.`);
410+ break;
411+ case "last_updated":
412+ assert(currentDS == sections[header.column],
413+ `New item was not "last_updated" with today's date.`);
414+ break;
415+ default:
416+ assert(false, "Unknown header: " ~ header.tsvName);
417+ }
418+ }
419+}
420+
421+@(`Can add a new item with status but no progress.`)
422+unittest
423+{
424+ const listPath = buildPath(tempDir(), "unittest3.tsv");
425+ scope(exit) remove(listPath);
426+
427+ MediaList* list = ml_open_list(listPath);
428+ assert(null !is list, "Failed to allocate memory for list.");
429+ scope(exit) ml_free_list(list);
430+
431+ MLError res = ml_send_command(list, MLCommand.add,
432+ ["Item 1", null, "COMPLETE"]);
433+ assert(MLError.success == res,
434+ `Failed to send MLCommand.add with ["Item 1", null, "COMPLETE"].`);
435+
436+ auto f = File(listPath);
437+ string line;
438+ bool readHeader = false;
439+ string[] sections;
440+
441+ while ((line = f.readln) !is null) {
442+ if (line[0] == '#')
443+ continue;
444+
445+ if (false == readHeader) {
446+ readHeader = true;
447+ continue;
448+ }
449+
450+ sections = line.strip().split("\t");
451+ }
452+
453+ Date currentD = cast(Date)Clock.currTime;
454+ string currentDS = format!"%d-%02d-%02d"(currentD.year,
455+ currentD.month, currentD.day);
456+
457+ MediaListHeader[] headers = ml_fetch_headers(list, null);
458+ assert(null !is headers, "Failed to fetch headers from list.");
459+ assert(sections.length == headers.length,
460+ "Differing amount of headers in unit test file.");
461+
462+ foreach(const ref header; headers) {
463+ switch (header.tsvName) {
464+ case "title":
465+ assert("Item 1" == sections[header.column],
466+ `New item with title "Item 1" wasn't saved.`);
467+ break;
468+ case "progress":
469+ assert("-/-" == sections[header.column],
470+ `New item with unspecified progress wasn't "-/-".`);
471+ break;
472+ case "status":
473+ assert("COMPLETE" == sections[header.column],
474+ `New item with status "COMPLETE" wasn't saved.`);
475+ break;
476+ case "start_date":
477+ assert("" == sections[header.column],
478+ `New item does not have blank "start_date" field.`);
479+ break;
480+ case "end_date":
481+ assert("" == sections[header.column],
482+ `New item does not have blank "end_date" field.`);
483+ break;
484+ case "last_updated":
485+ assert(currentDS == sections[header.column],
486+ `New item was not "last_updated" with today's date.`);
487+ break;
488+ default:
489+ assert(false, "Unknown header: " ~ header.tsvName);
490+ }
491+ }
492+}
493+
494+@(`Can add a new item with progress and status.`)
495+unittest
496+{
497+ const listPath = buildPath(tempDir(), "unittest3.tsv");
498+ scope(exit) remove(listPath);
499+
500+ MediaList* list = ml_open_list(listPath);
501+ assert(null !is list, "Failed to allocate memory for list.");
502+ scope(exit) ml_free_list(list);
503+
504+ MLError res = ml_send_command(list, MLCommand.add,
505+ ["Item 1", "10/-", "COMPLETE"]);
506+ assert(MLError.success == res,
507+ `Failed to send MLCommand.add with ["Item 1", "10/-", "COMPLETE"].`);
508+
509+ auto f = File(listPath);
510+ string line;
511+ bool readHeader = false;
512+ string[] sections;
513+
514+ while ((line = f.readln) !is null) {
515+ if (line[0] == '#')
516+ continue;
517+
518+ if (false == readHeader) {
519+ readHeader = true;
520+ continue;
521+ }
522+
523+ sections = line.strip().split("\t");
524+ }
525+
526+ Date currentD = cast(Date)Clock.currTime;
527+ string currentDS = format!"%d-%02d-%02d"(currentD.year,
528+ currentD.month, currentD.day);
529+
530+ MediaListHeader[] headers = ml_fetch_headers(list, null);
531+ assert(null !is headers, "Failed to fetch headers from list.");
532+ assert(sections.length == headers.length,
533+ "Differing amount of headers in unit test file.");
534+
535+ foreach(const ref header; headers) {
536+ switch (header.tsvName) {
537+ case "title":
538+ assert("Item 1" == sections[header.column],
539+ `New item with title "Item 1" wasn't saved.`);
540+ break;
541+ case "progress":
542+ assert("10/-" == sections[header.column],
543+ `New item with progress "10/-" wasn't saved.`);
544+ break;
545+ case "status":
546+ assert("COMPLETE" == sections[header.column],
547+ `New item with status "COMPLETE" wasn't saved.`);
548+ break;
549+ case "start_date":
550+ assert("" == sections[header.column],
551+ `New item does not have blank "start_date" field.`);
552+ break;
553+ case "end_date":
554+ assert("" == sections[header.column],
555+ `New item does not have blank "end_date" field.`);
556+ break;
557+ case "last_updated":
558+ assert(currentDS == sections[header.column],
559+ `New item was not "last_updated" with today's date.`);
560+ break;
561+ default:
562+ assert(false, "Unknown header: " ~ header.tsvName);
563+ }
564+ }
565+}
566+
567+@(`Can fetch a singular item by ID (2).`)
568+unittest
569+{
570+ MediaList* list = null;
571+ list = ml_open_list(buildPath(getcwd(), "unittest_fetchItem2.tsv"));
572+ assert(null !is list, "Failed to allocate memory for list.");
573+
574+ scope(exit) {
575+ ml_send_command(list, MLCommand.delete_, []);
576+ ml_free_list(list);
577+ }
578+
579+ MLError ec;
580+
581+ ec = ml_send_command(list, MLCommand.add, ["Item 1"]);
582+ assert(MLError.success == ec, `Failed to send MLCommand.add ["Item 1"].`);
583+
584+ ec = ml_send_command(list, MLCommand.add, ["Item 2", "1/-", "READING"]);
585+ assert(MLError.success == ec,
586+ `Failed to send MLCommand.add ["Item 2", "1/-", "READING"].`);
587+
588+ MediaListItem item2 = ml_fetch_item(list, 2, &ec);
589+ assert(MLError.success == ec,
590+ `Failed to fetch item 2 (ec == ` ~ to!string(ec) ~ `).`);
591+
592+ assert("Item 2" == item2.title,
593+ `ml_fetch_item(2).title != "Item 2" (got: ` ~ item2.title ~ `).`);
594+ assert("1/-" == item2.progress,
595+ `ml_fetch_item(2).progress != "1/-" (got: ` ~ item2.progress ~ `).`);
596+ assert("READING" == item2.status,
597+ `ml_fetch_item(2).status != "READING" (got: ` ~ item2.status ~ `).`);
598+ assert("" == item2.startDate,
599+ `ml_fetch_item(2).startDate != "" (got: ` ~ item2.startDate ~ `).`);
600+ assert("" == item2.endDate,
601+ `ml_fetch_item(2).endDate != "" (got: ` ~ item2.endDate ~ `).`);
602+}
603+
604+/*
605+ * Emulate pre-0.2 medialist files to confirm that headers can be in any order
606+ * and that it is case-insensitive.
607+ */
608+@(`Check header position variance and case-insensitivity.`)
609+unittest
610+{
611+ enum listName = "checkPre02MLFileHeaderOrder";
612+ enum fileName = listName ~ ".tsv";
613+
614+ /* case-insensitive */
615+ auto listFile = File(fileName, "w+");
616+ listFile.writeln("TITLE\tPROGRESS\tSTATUS");
617+ listFile.writeln("Item 1\t??/??\tUNKNOWN");
618+ listFile.close();
619+
620+ MediaList *list = ml_open_list(fileName);
621+ assert(null !is list, "Failed to allocate memory for list.");
622+
623+ /* shouldn't crash */
624+ MediaListHeader[] headers = ml_fetch_headers(list);
625+ assert(3 == headers.length, "ml_fetch_headers IS case-sensitive.");
626+
627+ /* shouldn't crash */
628+ MediaListItem[] items = ml_fetch_all(list);
629+
630+ ml_send_command(list, MLCommand.delete_, []);
631+ ml_free_list(list);
632+
633+ listFile = File(fileName, "w+");
634+ listFile.writeln ("PROGRESS\tTItLE\tstatus");
635+ listFile.writeln ("??/??\tItem 1\tUNKNOWN");
636+ listFile.close();
637+
638+ list = ml_open_list(fileName);
639+ items = ml_fetch_all(list);
640+
641+ headers = ml_fetch_headers(list);
642+ assert(3 == headers.length, "ml_fetch_headers USES hard-coded positions");
643+
644+ ml_send_command(list, MLCommand.delete_, []);
645+ ml_free_list(list);
646+}
647+
648+@(`Can fetch multiple items by ID (1, 4, and 2).`)
649+unittest
650+{
651+ enum listName = __FUNCTION__;
652+ enum fileName = listName ~ ".tsv";
653+
654+ MediaList* list = ml_open_list(fileName);
655+ assert(null !is list, "Failed to allocate memory for list.");
656+
657+ scope (exit) {
658+ /* delete the entire list and file. */
659+ ml_send_command(list, MLCommand.delete_, []);
660+ ml_free_list(list);
661+ }
662+
663+ MLError res;
664+
665+ res = ml_send_command(list, MLCommand.add, ["Item 1", "PLAN-TO-READ", "-/-"]);
666+ assert(MLError.success == res,
667+ `Failed to send MLCommand.add ["Item 1", "PLAN-TO-READ", "-/-"].`);
668+
669+ res = ml_send_command(list, MLCommand.add, ["Item 2", "READING", "10/12"]);
670+ assert(MLError.success == res,
671+ `Failed to send MLCommand.add ["Item 2", "READING", "10/12".`);
672+
673+ res = ml_send_command(list, MLCommand.add, ["Item 3"]);
674+ assert(MLError.success == res,
675+ `Failed to send MLCommand.add ["Item 3"].`);
676+
677+ res = ml_send_command(list, MLCommand.add, ["Item 4", "COMPLETE", "123/123"]);
678+ assert(MLError.success == res,
679+ `Failed to send MLCommand.add ["Item 4", "COMPLETE", "123/123"].`);
680+
681+ MediaListItem[] specificItems = ml_fetch_items(list, null, 1, 4, 2);
682+ assert(null !is specificItems,
683+ `ml_fetch_items(list, null, 1, 4, 2) returned null.`);
684+
685+ assert(3 == specificItems.length,
686+ `ml_fetch_items(list, null, 1, 4, 2) didn't return 3 items.`);
687+
688+ assert("Item 1" == specificItems[0].title,
689+ `specificItems[0].title != "Item 1".`);
690+
691+ assert("Item 2" == specificItems[1].title,
692+ `specificItems[1].title != "Item 2".`);
693+
694+ assert("Item 4" == specificItems[2].title,
695+ `specificItems[2].title != "Item 4".`);
696+}
697+
698+@(`Can update an item's title.`)
699+unittest
700+{
701+
702+ enum listName = __FUNCTION__;
703+ enum fileName = listName ~ ".tsv";
704+
705+ MediaList* list = ml_open_list(fileName);
706+ assert(null !is list, `Failed to allocate memory for list.`);
707+
708+ scope(exit) {
709+ ml_send_command(list, MLCommand.delete_, []);
710+ ml_free_list(list);
711+ }
712+
713+ MLError res;
714+
715+ res = ml_send_command(list, MLCommand.add, ["Ietm 1"]);
716+ assert(MLError.success == res, `Failed to send MLCommand.add ["Ietm 1"].`);
717+
718+ res = ml_send_command(list, MLCommand.update, ["1", "title::Item 1"]);
719+ assert(MLError.success == res,
720+ `Failed to send MLCommand.update ["1", "title::Item 1"].`);
721+
722+ MediaListItem item = ml_fetch_item(list, 1, &res);
723+ assert(MLError.success == res, `Failed to fetch item 1 from list.`);
724+
725+ assert("Item 1" == item.title,
726+ `Failed to update title from "Ietm 1" to "Item 1".`);
727+}
728+
729+@(`Can update an item's status.`)
730+unittest
731+{
732+ enum listName = __FUNCTION__;
733+ enum fileName = listName ~ ".tsv";
734+
735+ MediaList* list = ml_open_list(fileName);
736+ assert(null !is list, "Failed to allocate memory for list.");
737+
738+ scope(exit) {
739+ ml_send_command(list, MLCommand.delete_, []);
740+ ml_free_list(list);
741+ }
742+
743+ MLError res;
744+
745+ res = ml_send_command(list, MLCommand.add, ["Item 1", null, "PLAN-TO-READ"]);
746+ assert(MLError.success == res,
747+ `Failed to send MLCommand.add ["Item 1", null, "PLAN-TO-READ"].`);
748+
749+ res = ml_send_command(list, MLCommand.update, ["1", "status::READING"]);
750+ assert(MLError.success == res,
751+ `Failed to send MLCommand.update ["1", "status::READING"].`);
752+
753+ MediaListItem item = ml_fetch_item(list, 1, &res);
754+ assert(MLError.success == res, `Failed to fetch item 1 from list.`);
755+
756+ assert("READING" == item.status,
757+ `Failed to update status from "PLAN-TO-READ" to "READING".`);
758+}
759+
760+@(`Can update an item's progress.`)
761+unittest
762+{
763+ enum listName = __FUNCTION__;
764+ enum fileName = listName ~ ".tsv";
765+
766+ MediaList* list = ml_open_list(fileName);
767+ assert(null !is list, "Failed to allocate memory for list.");
768+
769+ scope(exit) {
770+ ml_send_command(list, MLCommand.delete_, []);
771+ ml_free_list(list);
772+ }
773+
774+ MLError res;
775+
776+ res = ml_send_command(list, MLCommand.add, ["Item 1"]);
777+ assert(MLError.success == res,
778+ `Failed to send MLCommand.add ["Item 1"].`);
779+
780+ res = ml_send_command(list, MLCommand.update, ["1", "progress::10/10"]);
781+ assert(MLError.success == res,
782+ `Failed to send MLCommand.update ["1", "progress::10/10"].`);
783+
784+ MediaListItem item = ml_fetch_item(list, 1);
785+ assert(true == item.valid, `Failed to fetch item 1 from list.`);
786+ assert("10/10" == item.progress,
787+ `Failed to update progress from "-/-" to "10/10".`);
788+}
789+
790+@(`Can update all aspects of an item.`)
791+unittest
792+{
793+ enum listName = __FUNCTION__;
794+ enum fileName = listName ~ ".tsv";
795+
796+ MediaList* list = ml_open_list(fileName);
797+ assert(null !is list, "Failed to allocate memory for list.");
798+
799+ scope(exit) {
800+ ml_send_command(list, MLCommand.delete_, []);
801+ ml_free_list(list);
802+ }
803+
804+ MLError res;
805+
806+ res = ml_send_command(list, MLCommand.add, ["Ietm 1"]);
807+ assert(MLError.success == res,
808+ `Failed to send MLCommand.add ["Ietm1"].`);
809+
810+ res = ml_send_command(list, MLCommand.update,
811+ ["1",
812+ "start_date::2021-02-16",
813+ "end_date::2022-01-20",
814+ "progress::60/100",
815+ "status::READING",
816+ "title::MediaList"]
817+ );
818+ assert(MLError.success == res,
819+ `Failed to send MLCommand.update ["1", "start_date::2021-02-16", ` ~
820+ `"end_date::2022-01-20", "progress::60/100", "status::READING", ` ~
821+ `"title::MediaList"].`);
822+
823+ MediaListItem item = ml_fetch_item(list, 1, &res);
824+ assert(MLError.success == res, `Failed to fetch item 1 from list.`);
825+
826+ assert("MediaList" == item.title,
827+ `Failed to update item title from "Ietm 1" to "MediaList".`);
828+ assert("60/100" == item.progress,
829+ `Failed to update item progress from "-/-" to "60/100".`);
830+ assert("READING" == item.status,
831+ `Failed to update item status from "UNKNOWN" to "READING".`);
832+ assert("2021-02-16" == item.startDate,
833+ `Failed to update item start date from "" to "2021-02-16".`);
834+ assert("2022-01-20" == item.endDate,
835+ `Failed to update item end date from "" to "2022-01-20".`);
836+
837+ Date date = cast(Date)Clock.currTime;
838+ string currentDate = format!"%d-%02d-%02d"(date.year, date.month, date.day);
839+ assert(currentDate == item.lastUpdated,
840+ `Failed to update item's last_update when sending MLCommand.update.`);
841+}