project_tests.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. import uuid
  2. from flask import url_for
  3. from pycs.database.Collection import Collection
  4. from pycs.database.File import File
  5. from pycs.database.Label import Label
  6. from pycs.database.Model import Model
  7. from pycs.database.Project import Project
  8. from pycs.database.Result import Result
  9. from pycs.interfaces.MediaFile import MediaFile
  10. from tests.base import BaseTestCase
  11. class _BaseProjectTests(BaseTestCase):
  12. def setupModels(self):
  13. model = Model.new(
  14. commit=False,
  15. name="TestModel",
  16. description="Model for a test case",
  17. root_folder="models/fixed_model",
  18. )
  19. model.supports = ["labeled-image"]
  20. model.flush()
  21. self.model = model
  22. def tearDown(self):
  23. self.model.delete()
  24. super().tearDown()
  25. class ProjectCreationTests(_BaseProjectTests):
  26. def setUp(self):
  27. super().setUp()
  28. self.url = url_for("create_project")
  29. def test_project_creation_without_name(self):
  30. self.assertEqual(0, Project.query.count())
  31. self.post(self.url, json=dict(
  32. # name="Some Project",
  33. description="Some description",
  34. model=self.model.id,
  35. label=None,
  36. external=None,
  37. ),
  38. status_code=400,
  39. )
  40. self.assertEqual(0, Project.query.count())
  41. def test_project_creation_without_description(self):
  42. self.assertEqual(0, Project.query.count())
  43. self.post(self.url, json=dict(
  44. name="Some Project",
  45. # description="Some description",
  46. model=self.model.id,
  47. label=None,
  48. external=None,
  49. ),
  50. status_code=400,
  51. )
  52. self.assertEqual(0, Project.query.count())
  53. def test_project_creation(self):
  54. self.assertEqual(0, Project.query.count())
  55. self.post(self.url, json=dict(
  56. name="Some Project",
  57. description="Some description",
  58. model=self.model.id,
  59. label=None,
  60. external=None,
  61. )
  62. )
  63. self.assertEqual(1, Project.query.count())
  64. project = Project.query.first()
  65. self.assertIsNotNone(project)
  66. self.assertIsNotNone(project.model)
  67. self.assertNotEqual(project.model.id, self.model.id,
  68. "The model shoud be copied")
  69. self.assertIsNone(project.label_provider)
  70. class ProjectDeletionTests(_BaseProjectTests):
  71. def setupModels(self):
  72. super().setupModels()
  73. self.project = Project.new(
  74. name="test_project",
  75. description="Project for a test case",
  76. model=self.model,
  77. root_folder="project_folder",
  78. external_data=False,
  79. data_folder="project_folder/data",)
  80. @property
  81. def url(self):
  82. return url_for("remove_project",
  83. project_id=self.project.id)
  84. def test_project_deletion(self):
  85. self.assertEqual(1, Project.query.count())
  86. self.post(self.url, json=dict(remove=True))
  87. self.assertEqual(0, Project.query.count())
  88. def test_project_deletion_without_flag(self):
  89. self.assertEqual(1, Project.query.count())
  90. self.post(self.url, json=dict(), status_code=400)
  91. self.assertEqual(1, Project.query.count())
  92. self.post(self.url, json=dict(remove=False), status_code=400)
  93. self.assertEqual(1, Project.query.count())
  94. def test_project_deletion_with_labels(self):
  95. self.assertEqual(1, Project.query.count())
  96. self.assertEqual(0, Label.query.count())
  97. for i in range(1, 11):
  98. self.project.create_label(name=f"Label_{i}")
  99. self.assertEqual(10, Label.query.count())
  100. self.post(self.url, json=dict(remove=True))
  101. self.assertEqual(0, Project.query.count())
  102. self.assertEqual(0, Label.query.count())
  103. class ProjectListTests(_BaseProjectTests):
  104. def test_list_projects(self):
  105. self.assertEqual(0, Project.query.count())
  106. for i in range(1, 11):
  107. Project.new(
  108. name=f"TestProject{i}",
  109. description=f"Project for a test case #{i}",
  110. model=self.model,
  111. root_folder=f"project_folder{i}",
  112. external_data=False,
  113. data_folder=f"project_folder{i}/data",
  114. )
  115. self.assertEqual(10, Project.query.count())
  116. response = self.get(url_for("list_projects"))
  117. self.assertTrue(response.is_json)
  118. content = response.json
  119. self.assertEqual(10, len(content))
  120. for entry in content:
  121. project = Project.query.get(entry["id"])
  122. self.assertIsNotNone(project)
  123. self.assertDictEqual(entry, project.serialize())
  124. def test_list_project_collections(self):
  125. project = Project.new(
  126. name="TestProject",
  127. description="Project for a test case",
  128. model=self.model,
  129. root_folder="project_folder",
  130. external_data=False,
  131. data_folder="project_folder/data",
  132. )
  133. self.assertEqual(0, Collection.query.count())
  134. for i in range(1, 11):
  135. project.create_collection(
  136. reference=f"collection_{i}",
  137. name=f"Some collection {i}",
  138. description=f"A description {i}",
  139. position=i,
  140. autoselect=i == 1
  141. )
  142. self.assertEqual(10, Collection.query.count())
  143. response = self.get(url_for("list_collections",
  144. project_id=project.id))
  145. self.assertTrue(response.is_json)
  146. content = response.json
  147. self.assertEqual(10, len(content))
  148. for entry in content:
  149. collection = Collection.query.get(entry["id"])
  150. self.assertIsNotNone(collection)
  151. self.assertDictEqual(entry, collection.serialize())
  152. def test_list_all_files(self):
  153. project = Project.new(
  154. name="TestProject",
  155. description="Project for a test case",
  156. model=self.model,
  157. root_folder="project_folder",
  158. external_data=False,
  159. data_folder="project_folder/data",
  160. )
  161. self.assertEqual(0, File.query.count())
  162. files = []
  163. for i in range(1, 11):
  164. file_uuid = str(uuid.uuid1())
  165. file, is_new = project.add_file(
  166. uuid=file_uuid,
  167. file_type="image",
  168. name=f"name{i}",
  169. filename=f"image_{i:03d}",
  170. extension=".jpg",
  171. size=32*1024,
  172. )
  173. self.assertTrue(is_new)
  174. files.append(file)
  175. self.assertEqual(10, File.query.count())
  176. response = self.get(url_for("list_all_files",
  177. project_id=project.id))
  178. self.assertTrue(response.is_json)
  179. _content = response.json
  180. count = _content["count"]
  181. content = _content["files"]
  182. self.assertEqual(10, count)
  183. self.assertEqual(10, len(content))
  184. for file, entry in zip(files, content):
  185. self.assertDictEqual(entry, file.serialize())
  186. def test_list_some_files(self):
  187. project = Project.new(
  188. name="TestProject",
  189. description="Project for a test case",
  190. model=self.model,
  191. root_folder="project_folder",
  192. external_data=False,
  193. data_folder="project_folder/data",
  194. )
  195. self.assertEqual(0, File.query.count())
  196. files = []
  197. for i in range(1, 11):
  198. file_uuid = str(uuid.uuid1())
  199. file, is_new = project.add_file(
  200. uuid=file_uuid,
  201. file_type="image",
  202. name=f"name{i}",
  203. filename=f"image_{i:03d}",
  204. extension=".jpg",
  205. size=32*1024,
  206. )
  207. self.assertTrue(is_new)
  208. files.append(file)
  209. self.assertEqual(10, File.query.count())
  210. for start, length in [(0, 5), (0, 15), (5, 3), (5, 8)]:
  211. response = self.get(url_for("list_files",
  212. project_id=project.id,
  213. start=start, length=length))
  214. self.assertTrue(response.is_json)
  215. _content = response.json
  216. count = _content["count"]
  217. content = _content["files"]
  218. self.assertEqual(len(files), count)
  219. self.assertEqual(min(len(files), start+length) - start, len(content))
  220. for file, entry in zip(files[start:start+length], content):
  221. self.assertDictEqual(entry, file.serialize())
  222. def test_list_collection_files_of_non_existing_collection(self):
  223. project = Project.new(
  224. name="TestProject",
  225. description="Project for a test case",
  226. model=self.model,
  227. root_folder="project_folder",
  228. external_data=False,
  229. data_folder="project_folder/data",
  230. )
  231. url = url_for("list_collection_files",
  232. project_id=project.id, collection_id=42,
  233. start=0, length=30)
  234. self.get(url, status_code=404)
  235. def test_list_collection_files(self):
  236. project = Project.new(
  237. name="TestProject",
  238. description="Project for a test case",
  239. model=self.model,
  240. root_folder="project_folder",
  241. external_data=False,
  242. data_folder="project_folder/data",
  243. )
  244. self.assertEqual(1, Project.query.count())
  245. collections = {}
  246. for i in range(1, 3):
  247. collection, is_new = project.create_collection(
  248. reference=f"collection_{i}",
  249. name=f"Some collection {i}",
  250. description=f"A description {i}",
  251. position=i,
  252. autoselect=i == 1
  253. )
  254. self.assertTrue(is_new)
  255. collection_files = []
  256. for j in range(1, 4):
  257. file_uuid = str(uuid.uuid1())
  258. file, is_new = collection.add_file(
  259. uuid=file_uuid,
  260. file_type="image",
  261. name=f"col_{i}_name{j}",
  262. filename=f"col_{i}_image_{j:03d}",
  263. extension=".jpg",
  264. size=32*1024,
  265. )
  266. self.assertTrue(is_new)
  267. collection_files.append(file)
  268. collections[collection.id] = collection_files
  269. files = []
  270. for j in range(1, 4):
  271. file_uuid = str(uuid.uuid1())
  272. file, is_new = project.add_file(
  273. uuid=file_uuid,
  274. file_type="image",
  275. name=f"name{j}",
  276. filename=f"image_{j:03d}",
  277. extension=".jpg",
  278. size=32*1024,
  279. )
  280. self.assertTrue(is_new)
  281. files.append(file)
  282. collections[0] = files
  283. self.assertEqual(2, Collection.query.filter(Collection.project_id==project.id).count())
  284. self.assertEqual(6, File.query.filter(
  285. File.project_id == project.id,
  286. File.collection_id != None,
  287. ).count())
  288. self.assertEqual(3, File.query.filter(
  289. File.project_id == project.id,
  290. File.collection_id == None,
  291. ).count())
  292. for collection_id, files in collections.items():
  293. for start, length in [(0, 5), (0, 15), (1, 3), (1, 8)]:
  294. response = self.get(url_for("list_collection_files",
  295. project_id=project.id, collection_id=collection_id,
  296. start=start, length=length))
  297. self.assertTrue(response.is_json)
  298. _content = response.json
  299. count = _content["count"]
  300. content = _content["files"]
  301. self.assertEqual(len(files), count)
  302. self.assertEqual(min(len(files), start+length) - start, len(content))
  303. for file, entry in zip(files[start:start+length], content):
  304. self.assertDictEqual(entry, file.serialize())
  305. def test_list_project_results(self):
  306. project = Project.new(
  307. name="test_project",
  308. description="Project for a test case",
  309. model=self.model,
  310. root_folder="project_folder",
  311. external_data=False,
  312. data_folder="project_folder/data",)
  313. url = url_for("get_project_results", project_id=42)
  314. self.get(url, status_code=404)
  315. files = []
  316. for i in range(1, 4):
  317. file_uuid = str(uuid.uuid1())
  318. file, is_new = project.add_file(
  319. uuid=file_uuid,
  320. file_type="image",
  321. name=f"name{i}",
  322. filename=f"image_{i:03d}",
  323. extension=".jpg",
  324. size=32*1024,
  325. )
  326. self.assertTrue(is_new)
  327. files.append(file)
  328. file.create_result(
  329. origin="user",
  330. result_type="bounding-box",
  331. label=None,
  332. data=dict(x=0, y=0, w=1, h=1)
  333. )
  334. self.assertEqual(3, File.query.count())
  335. self.assertEqual(3, Result.query.count())
  336. url = url_for("get_project_results", project_id=project.id)
  337. response = self.get(url)
  338. self.assertTrue(response.is_json)
  339. content = response.json
  340. self.assertTrue(project.files.count(), len(content))
  341. media_files = {f.filename: MediaFile(f, None) for f in project.files.all()}
  342. for entry in content:
  343. media_file = media_files[entry["filename"]]
  344. self.assertDictEqual(media_file.serialize(), entry)
  345. def test_get_project_model(self):
  346. project = Project.new(
  347. name="test_project",
  348. description="Project for a test case",
  349. model=self.model,
  350. root_folder="project_folder",
  351. external_data=False,
  352. data_folder="project_folder/data",)
  353. url = url_for("get_project_model", project_id=42)
  354. self.get(url, status_code=404)
  355. url = url_for("get_project_model", project_id=project.id)
  356. response = self.get(url)
  357. self.assertTrue(response.is_json)
  358. content = response.json
  359. self.assertDictEqual(project.model.serialize(), content)
  360. class ProjectEditTests(_BaseProjectTests):
  361. def setupModels(self):
  362. super().setupModels()
  363. self.project = Project.new(
  364. name="test_project",
  365. description="Project for a test case",
  366. model=self.model,
  367. root_folder="project_folder",
  368. external_data=False,
  369. data_folder="project_folder/data",)
  370. def test_name_edit(self):
  371. name = "new_name"
  372. url = url_for("edit_project_name", project_id=self.project.id)
  373. self.post(url, json=dict(name=name))
  374. self.assertEqual(name, self.project.name)
  375. def test_name_edit_missing_argument(self):
  376. url = url_for("edit_project_name", project_id=self.project.id)
  377. self.post(url, json=dict(), status_code=400)
  378. self.assertEqual("test_project", self.project.name)
  379. def test_description_edit(self):
  380. description = "New description for the project"
  381. url = url_for("edit_project_description", project_id=self.project.id)
  382. self.post(url, json=dict(description=description))
  383. self.assertEqual(description, self.project.description)
  384. def test_description_edit_missing_argument(self):
  385. url = url_for("edit_project_description", project_id=self.project.id)
  386. self.post(url, json=dict(), status_code=400)
  387. self.assertEqual("Project for a test case", self.project.description)