class UploadedFileManagerTest(unittest.TestCase):
    def setUp(self):
        self.mgr = UploadedFileManager()
        self.filemgr_events = []
        self.mgr.on_files_added.connect(self._on_files_added)

    def _on_files_added(self, file_list, **kwargs):
        self.filemgr_events.append(file_list)

    def test_add_file(self):
        self.assertIsNone(self.mgr.get_files("non-report", "non-widget"))

        event1 = UploadedFileList("session", "widget", [file1])
        event2 = UploadedFileList("session", "widget", [file2])

        self.mgr.add_files("session", "widget", [file1])
        self.assertEqual([file1], self.mgr.get_files("session", "widget"))
        self.assertEqual([event1], self.filemgr_events)

        # Add another file with the same ID
        self.mgr.add_files("session", "widget", [file2])
        self.assertEqual([file2], self.mgr.get_files("session", "widget"))
        self.assertEqual([event1, event2], self.filemgr_events)

    def test_remove_file(self):
        # This should not error.
        self.mgr.remove_files("non-report", "non-widget")

        self.mgr.add_files("session", "widget", [file1])
        self.assertEqual([file1], self.mgr.get_files("session", "widget"))

        self.mgr.remove_files("session", "widget")
        self.assertIsNone(self.mgr.get_files("session", "widget"))

    def test_remove_all_files(self):
        # This should not error.
        self.mgr.remove_session_files("non-report")

        # Add two files with different session IDs, but the same widget ID.
        self.mgr.add_files("session1", "widget", [file1])
        self.mgr.add_files("session2", "widget", [file1])

        event1 = UploadedFileList("session1", "widget", [file1])
        event2 = UploadedFileList("session2", "widget", [file1])

        self.mgr.remove_session_files("session1")
        self.assertIsNone(self.mgr.get_files("session1", "widget"))
        self.assertEqual([file1], self.mgr.get_files("session2", "widget"))
        self.assertEqual([event1, event2], self.filemgr_events)
class UploadFileRequestHandlerTest(tornado.testing.AsyncHTTPTestCase):
    """Tests the /upload_file endpoint."""
    def get_app(self):
        self.file_mgr = UploadedFileManager()
        return tornado.web.Application([("/upload_file",
                                         UploadFileRequestHandler,
                                         dict(file_mgr=self.file_mgr))])

    def _upload_files(self, params):
        # We use requests.Request to construct our multipart/form-data request
        # here, because they are absurdly fiddly to compose, and Tornado
        # doesn't include a utility for building them. We then use self.fetch()
        # to actually send the request to the test server.
        req = requests.Request(method="POST",
                               url=self.get_url("/upload_file"),
                               files=params).prepare()

        return self.fetch("/upload_file",
                          method=req.method,
                          headers=req.headers,
                          body=req.body)

    def test_upload_one_file(self):
        """Uploading a file should populate our file_mgr."""
        file = UploadedFile("image.png", b"123")
        params = {
            file.name: file,
            "sessionId": (None, "fooReport"),
            "widgetId": (None, "barWidget"),
        }
        response = self._upload_files(params)
        self.assertEqual(200, response.code)
        self.assertEqual([file],
                         self.file_mgr.get_files("fooReport", "barWidget"))

    def test_upload_multiple_files(self):
        file1 = UploadedFile("image1.png", b"123")
        file2 = UploadedFile("image2.png", b"456")
        file3 = UploadedFile("image3.png", b"789")

        params = {
            file1.name: file1,
            file2.name: file2,
            file3.name: file3,
            "sessionId": (None, "fooReport"),
            "widgetId": (None, "barWidget"),
        }
        response = self._upload_files(params)
        self.assertEqual(200, response.code)
        self.assertEqual(
            sorted([file1, file2, file3], key=_get_filename),
            sorted(self.file_mgr.get_files("fooReport", "barWidget"),
                   key=_get_filename),
        )

    def test_missing_params(self):
        """Missing params in the body should fail with 400 status."""
        params = {
            "image.png": ("image.png", b"1234"),
            "sessionId": (None, "fooReport"),
            # "widgetId": (None, 'barWidget'),
        }

        response = self._upload_files(params)
        self.assertEqual(400, response.code)
        self.assertIn("Missing 'widgetId'", response.reason)

    def test_missing_file(self):
        """Missing file should fail with 400 status."""
        params = {
            # "image.png": ("image.png", b"1234"),
            "sessionId": (None, "fooReport"),
            "widgetId": (None, "barWidget"),
        }
        response = self._upload_files(params)
        self.assertEqual(400, response.code)
        self.assertIn("Expected at least 1 file, but got 0", response.reason)