/ test / python / testapi / testencoding.py
testencoding.py
  1  """
  2  Encoding module tests
  3  """
  4  
  5  import base64
  6  import os
  7  import tempfile
  8  import unittest
  9  import urllib.parse
 10  
 11  from io import BytesIO
 12  from unittest.mock import patch
 13  
 14  import msgpack
 15  import numpy as np
 16  import PIL
 17  
 18  from fastapi.testclient import TestClient
 19  
 20  from txtai.api import application
 21  from txtai.api.responses import JSONEncoder
 22  
 23  # pylint: disable=C0411
 24  from utils import Utils
 25  
 26  # Configuration for image storage
 27  INDEX = """
 28  # Allow indexing of documents
 29  writable: %s
 30  
 31  embeddings:
 32      defaults: False
 33      content: True
 34      objects: %s
 35  """
 36  
 37  
 38  class TestEncoding(unittest.TestCase):
 39      """
 40      API tests for response encoding
 41      """
 42  
 43      @staticmethod
 44      @patch.dict(os.environ, {"CONFIG": os.path.join(tempfile.gettempdir(), "testapi.yml"), "API_CLASS": "txtai.api.API"})
 45      def start(yaml):
 46          """
 47          Starts a mock FastAPI client.
 48  
 49          Args:
 50              yaml: input configuration
 51          """
 52  
 53          config = os.path.join(tempfile.gettempdir(), "testapi.yml")
 54  
 55          with open(config, "w", encoding="utf-8") as output:
 56              output.write(yaml)
 57  
 58          # Create new application and set on client
 59          application.app = application.create()
 60          client = TestClient(application.app)
 61          application.start()
 62  
 63          return client
 64  
 65      @classmethod
 66      def setUpClass(cls):
 67          """
 68          Create API client on creation of class.
 69          """
 70  
 71          cls.client = TestEncoding.start(INDEX % ("True", "image"))
 72  
 73      def testImages(self):
 74          """
 75          Test image encoding
 76          """
 77  
 78          with open(Utils.PATH + "/books.jpg", "rb") as f:
 79              self.client.post("addimage", data={"uid": 0}, files={"data": f})
 80              self.client.get("index")
 81  
 82          query = urllib.parse.quote_plus("select id, object from txtai limit 1")
 83          results = self.client.get(f"search?query={query}").json()
 84  
 85          # Test reading image
 86          self.assertIsInstance(PIL.Image.open(BytesIO(base64.b64decode(results[0]["object"]))), PIL.Image.Image)
 87  
 88      def testInvalidInputs(self):
 89          """
 90          Test invalid parameter inputs
 91          """
 92  
 93          response = self.client.post("addimage", data={"uid": [0, 1]}, files={"data": b"123"})
 94          self.assertEqual(response.status_code, 422)
 95  
 96          response = self.client.post("addobject", data={"uid": [0, 1]}, files={"data": b"123"})
 97          self.assertEqual(response.status_code, 422)
 98  
 99      def testInvalidJSON(self):
100          """
101          Test that invalid JSON raises an exception
102          """
103  
104          with self.assertRaises(TypeError):
105              JSONEncoder().encode(np.random.rand(1, 1))
106  
107      def testMessagePack(self):
108          """
109          Test message pack encoding
110          """
111  
112          # Validate binary encoding
113          results = self.client.get("count", headers={"Accept": "application/msgpack"}).content
114          self.assertEqual(results, b"\x01")
115  
116          # Validate query result
117          query = urllib.parse.quote_plus("select id, object from txtai limit 1")
118          results = self.client.get(f"search?query={query}", headers={"Accept": "application/msgpack"}).content
119          results = msgpack.unpackb(results)
120  
121          # Test reading image
122          self.assertIsInstance(PIL.Image.open(BytesIO(results[0]["object"])), PIL.Image.Image)
123  
124      def testObjects(self):
125          """
126          Test object encoding
127          """
128  
129          # Recreate model with standard object encoding
130          self.client = TestEncoding.start(INDEX % ("True", "True"))
131  
132          # Test various formats
133          self.client.post("addobject", data={"uid": "id0"}, files={"data": b"1234"})
134          self.client.post("addobject", files={"data": b"ABC"})
135          self.client.post("addobject", data={"uid": "id1", "field": "object"}, files={"data": b"A1234"})
136          self.client.get("index")
137  
138          query = urllib.parse.quote_plus("select id, object from txtai where id = 'id0' limit 1")
139          results = self.client.get(f"search?query={query}").json()
140          self.assertEqual(base64.b64decode(results[0]["object"]), b"1234")
141  
142          # Test with messagepack encoding
143          results = self.client.get(f"search?query={query}", headers={"Accept": "application/msgpack"}).content
144          results = msgpack.unpackb(results)
145          self.assertEqual(results[0]["object"], b"1234")
146  
147          count = self.client.get("count").json()
148          self.assertEqual(count, 3)
149  
150      def testReadOnly(self):
151          """
152          Test read only indexes
153          """
154  
155          # Recreate model with standard object encoding
156          self.client = TestEncoding.start(INDEX % ("False", "True"))
157  
158          # Test errors raised for write operations
159          with open(Utils.PATH + "/books.jpg", "rb") as f:
160              response = self.client.post("addimage", data={"uid": 0}, files={"data": f})
161              self.assertEqual(response.status_code, 403)
162  
163          self.assertEqual(self.client.post("addobject", data={"uid": 0}, files={"data": b"1234"}).status_code, 403)