diff --git a/tests/fake_api.py b/tests/fake_api.py new file mode 100644 index 00000000..b9956a7c --- /dev/null +++ b/tests/fake_api.py @@ -0,0 +1,231 @@ +import json + +CURRENT_VERSION = 'v1.4' + +FAKE_CONTAINER_ID = '3cc2351ab11b' +FAKE_IMAGE_ID = 'e9aa60c60128' + +FAKE_INSPECT_DATA = { + "ID": "3cc2351ab11bca2e319f49b547834f550eb0c0505a636dc4d24c5b5e03845927", + "Created": "2013-09-25T14:01:18.867354259+02:00", + "Path": "/bin/sh", + "Args": [ + "-c", + "mkdir -p /tmp/test" + ], + "Config": { + "Hostname": FAKE_CONTAINER_ID, + "Domainname": "", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 0, + "AttachStdin": False, + "AttachStdout": False, + "AttachStderr": False, + "PortSpecs": [ + "8080" + ], + "Tty": False, + "OpenStdin": False, + "StdinOnce": False, + "Env": [ + "HOME=/", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Cmd": None, + "Dns": None, + "Image": "b9517f6e2d833745eb5f893ea901abe5ea8247fab8e569e1d3280881ab84284b", + "Volumes": None, + "VolumesFrom": "", + "WorkingDir": "", + "Entrypoint": None, + "NetworkDisabled": False, + "Privileged": False + }, + "State": { + "Running": False, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-09-25T14:01:18.869545111+02:00", + "Ghost": False + }, + "Image": "9330a90e5753f16df757d865700365263946bd7e6b6439e44622e5952bb5033e", + "NetworkSettings": { + "IPAddress": "", + "IPPrefixLen": 0, + "Gateway": "", + "Bridge": "", + "PortMapping": None + }, + "SysInitPath": "/opt/docker/docker", + "ResolvConfPath": "/etc/resolv.conf", + "HostnamePath": "/var/lib/docker/containers/800680f2e4ccca2e319f49b547834f550eb0c0505a636dc4d24c5b5e03845927/hostname", + "HostsPath": "/var/lib/docker/containers/800680f2e4ccca2e319f49b547834f550eb0c0505a636dc4d24c5b5e03845927/hosts", + "Volumes": {}, + "VolumesRW": {} +} + + +### Each method is prefixed with HTTP method (get, post...) +### for clarity and readability + + +def get_fake_version(): + status_code = 200 + response = json.dumps({'GoVersion': '1', 'Version': '1.1.1'}) + return status_code, response + + +def get_fake_info(): + status_code = 200 + response = json.dumps({'Containers': 1, 'Images': 1, 'Debug': ''}) + return status_code, response + + +def get_fake_search(): + status_code = 200 + response = json.dumps([{'Name': 'busybox', 'Description': 'Fake Description'}]) + return status_code, response + + +def get_fake_images(): + status_code = 200 + response = json.dumps([ + {'Id': FAKE_IMAGE_ID, 'Created': '2 days ago', 'Repository': 'busybox', 'Tag': 'latest'} + ]) + return status_code, response + + +def get_fake_containers(): + status_code = 200 + response = json.dumps([ + {'Id': FAKE_CONTAINER_ID, + 'Image': 'busybox:latest', + 'Created': '2 days ago', + 'Command': 'true', + 'Status': 'fake status'} + ]) + return status_code, response + + +def post_fake_start_container(): + status_code = 200 + response = json.dumps({'Id': FAKE_CONTAINER_ID}) + return status_code, response + + +def post_fake_create_container(): + status_code = 200 + response = json.dumps({'Id': FAKE_CONTAINER_ID}) + return status_code, response + + +def get_fake_inspect_container(): + status_code = 200 + response = json.dumps({ + 'Id': FAKE_CONTAINER_ID, + 'Config': {'Privileged': True}, + 'ID': FAKE_CONTAINER_ID, + 'Image': 'busybox:latest', + "State": { + "Running": True, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-09-25T14:01:18.869545111+02:00", + "Ghost": False + }, + + + }) + return status_code, response + + +def get_fake_wait(): + status_code = 200 + response = json.dumps({'StatusCode': 0}) + return status_code, response + + +def get_fake_logs(): + status_code = 200 + response = 'Flowering Nights (Sakuya Iyazoi)' + return status_code, response + + +def get_fake_diff(): + status_code = 200 + response = json.dumps([{'Path': '/test', 'Kind': 1}]) + return status_code, response + + +def post_fake_stop_container(): + status_code = 200 + response = json.dumps({'Id': FAKE_CONTAINER_ID}) + return status_code, response + + +def post_fake_kill_container(): + status_code = 200 + response = json.dumps({'Id': FAKE_CONTAINER_ID}) + return status_code, response + + +def post_fake_restart_container(): + status_code = 200 + response = json.dumps({'Id': FAKE_CONTAINER_ID}) + return status_code, response + + +def delete_fake_remove_container(): + status_code = 200 + response = json.dumps({'Id': FAKE_CONTAINER_ID}) + return status_code, response + + +def post_fake_image_create(): + status_code = 200 + response = json.dumps({'Id': FAKE_IMAGE_ID}) + return status_code, response + + +def delete_fake_remove_image(): + status_code = 200 + response = json.dumps({'Id': FAKE_IMAGE_ID}) + return status_code, response + + +def post_fake_commit(): + status_code = 200 + response = json.dumps({'Id': FAKE_CONTAINER_ID}) + return status_code, response + + +def post_fake_build_container(): + status_code = 200 + response = json.dumps({'Id': FAKE_CONTAINER_ID}) + return status_code, response + + +## maps real api url to fake response callback +fake_responses = { + 'unix://var/run/docker.sock/{0}/version'.format(CURRENT_VERSION): get_fake_version, + 'unix://var/run/docker.sock/{0}/info'.format(CURRENT_VERSION): get_fake_info, + 'unix://var/run/docker.sock/{0}/images/search'.format(CURRENT_VERSION): get_fake_search, + 'unix://var/run/docker.sock/{0}/images/json'.format(CURRENT_VERSION): get_fake_images, + 'unix://var/run/docker.sock/{0}/containers/ps'.format(CURRENT_VERSION): get_fake_containers, + 'unix://var/run/docker.sock/{0}/containers/3cc2351ab11b/start'.format(CURRENT_VERSION): post_fake_start_container, + 'unix://var/run/docker.sock/{0}/containers/3cc2351ab11b/json'.format(CURRENT_VERSION): get_fake_inspect_container, + 'unix://var/run/docker.sock/{0}/containers/3cc2351ab11b/wait'.format(CURRENT_VERSION): get_fake_wait, + 'unix://var/run/docker.sock/{0}/containers/3cc2351ab11b/attach'.format(CURRENT_VERSION): get_fake_logs, + 'unix://var/run/docker.sock/{0}/containers/3cc2351ab11b/changes'.format(CURRENT_VERSION): get_fake_diff, + 'unix://var/run/docker.sock/{0}/containers/3cc2351ab11b/stop'.format(CURRENT_VERSION): post_fake_stop_container, + 'unix://var/run/docker.sock/{0}/containers/3cc2351ab11b/kill'.format(CURRENT_VERSION): post_fake_kill_container, + 'unix://var/run/docker.sock/{0}/containers/3cc2351ab11b/restart'.format(CURRENT_VERSION): post_fake_restart_container, + 'unix://var/run/docker.sock/{0}/containers/3cc2351ab11b'.format(CURRENT_VERSION): delete_fake_remove_container, + 'unix://var/run/docker.sock/{0}/images/create'.format(CURRENT_VERSION): post_fake_image_create, + 'unix://var/run/docker.sock/{0}/images/e9aa60c60128'.format(CURRENT_VERSION): delete_fake_remove_image, + 'unix://var/run/docker.sock/{0}/commit'.format(CURRENT_VERSION): post_fake_commit, + 'unix://var/run/docker.sock/{0}/containers/create'.format(CURRENT_VERSION): post_fake_create_container, + 'unix://var/run/docker.sock/{0}/build'.format(CURRENT_VERSION): post_fake_build_container +} diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 00000000..cf4bdb28 --- /dev/null +++ b/tests/test.py @@ -0,0 +1,377 @@ +# Copyright 2013 dotCloud inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +import os +from StringIO import StringIO +import tempfile +import unittest + +import docker +import six + + +import requests +from requests import structures +import datetime +import json +from fake_api import fake_responses, FAKE_CONTAINER_ID, FAKE_IMAGE_ID + +# FIXME: missing tests for +# export; history; import_image; insert; port; push; tag + + +def response(status_code=200, content='', headers=None, reason=None, elapsed=0, + request=None): + res = requests.Response() + res.status_code = status_code + if isinstance(content, dict): + content = json.dumps(content) + res._content = content + res.headers = structures.CaseInsensitiveDict(headers or {}) + res.reason = reason + res.elapsed = datetime.timedelta(elapsed) + return res + + +def fake_get(self, url, **kwargs): + status_code, content = fake_responses[url]() + return response(status_code=status_code, content=content) + + +def fake_post(self, url, data=None, **kwargs): + status_code, content = fake_responses[url]() + return response(status_code=status_code, content=content) + + +def fake_put(self, url, data=None, **kwargs): + status_code, content = fake_responses[url]() + return response(status_code=status_code, content=content) + + +def fake_delete(self, url, data=None, **kwargs): + status_code, content = fake_responses[url]() + return response(status_code=status_code, content=content) + + +docker.Client.get = fake_get +docker.Client.post = fake_post +docker.Client.put = fake_put +docker.Client.delete = fake_delete + + +class BaseTestCase(unittest.TestCase): + + def setUp(self): + self.client = docker.Client() + + +######################### +## INFORMATION TESTS ## +######################### + + +class TestVersion(BaseTestCase): + def runTest(self): + try: + self.client.version() + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestInfo(BaseTestCase): + def runTest(self): + try: + self.client.info() + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestSearch(BaseTestCase): + def runTest(self): + try: + self.client.search('busybox') + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +# ################### +# ## LISTING TESTS ## +# ################### + + +class TestImages(BaseTestCase): + def runTest(self): + try: + self.client.images(all=True) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestImageIds(BaseTestCase): + def runTest(self): + try: + self.client.images(quiet=True) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestListContainers(BaseTestCase): + def runTest(self): + try: + self.client.containers(all=True) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +##################### +## CONTAINER TESTS ## +##################### + + +class TestCreateContainer(BaseTestCase): + def runTest(self): + try: + self.client.create_container('busybox', 'true') + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestCreateContainerWithBinds(BaseTestCase): + def runTest(self): + mount_dest = '/mnt' + mount_origin = '/tmp' + + try: + self.client.create_container('busybox', + ['ls', mount_dest], volumes={mount_dest: {}}) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestCreateContainerPrivileged(BaseTestCase): + def runTest(self): + try: + self.client.create_container('busybox', 'true', privileged=True) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestStartContainer(BaseTestCase): + def runTest(self): + try: + self.client.start(FAKE_CONTAINER_ID) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestStartContainerWithBinds(BaseTestCase): + def runTest(self): + try: + mount_dest = '/mnt' + mount_origin = '/tmp' + self.client.start(FAKE_CONTAINER_ID, binds={mount_origin: mount_dest}) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestStartContainerWithDictInsteadOfId(BaseTestCase): + def runTest(self): + try: + self.client.start({'Id': FAKE_CONTAINER_ID}) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestWait(BaseTestCase): + def runTest(self): + try: + self.client.wait(FAKE_CONTAINER_ID) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestWaitWithDictInsteadOfId(BaseTestCase): + def runTest(self): + try: + self.client.wait({'Id': FAKE_CONTAINER_ID}) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestLogs(BaseTestCase): + def runTest(self): + try: + self.client.logs(FAKE_CONTAINER_ID) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestLogsWithDictInsteadOfId(BaseTestCase): + def runTest(self): + try: + self.client.logs({'Id': FAKE_CONTAINER_ID}) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestDiff(BaseTestCase): + def runTest(self): + try: + self.client.diff(FAKE_CONTAINER_ID) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestDiffWithDictInsteadOfId(BaseTestCase): + def runTest(self): + try: + self.client.diff({'Id': FAKE_CONTAINER_ID}) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestStop(BaseTestCase): + def runTest(self): + try: + self.client.stop(FAKE_CONTAINER_ID, timeout=2) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestStopWithDictInsteadOfId(BaseTestCase): + def runTest(self): + try: + self.client.stop({'Id': FAKE_CONTAINER_ID}, timeout=2) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestKill(BaseTestCase): + def runTest(self): + try: + self.client.kill(FAKE_CONTAINER_ID) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestKillWithDictInsteadOfId(BaseTestCase): + def runTest(self): + try: + self.client.kill({'Id': FAKE_CONTAINER_ID}) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestRestart(BaseTestCase): + def runTest(self): + try: + self.client.restart(FAKE_CONTAINER_ID, timeout=2) + except Exception as e: + self.fail('Command should not raise exception : ' + str(e)) + + +class TestRestartWithDictInsteadOfId(BaseTestCase): + def runTest(self): + try: + self.client.restart({'Id': FAKE_CONTAINER_ID}, timeout=2) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestRemoveContainer(BaseTestCase): + def runTest(self): + try: + self.client.remove_container(FAKE_CONTAINER_ID) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestRemoveContainerWithDictInsteadOfId(BaseTestCase): + def runTest(self): + try: + self.client.remove_container({'Id': FAKE_CONTAINER_ID}) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +# ################## +# ## IMAGES TESTS ## +# ################## + +class TestPull(BaseTestCase): + def runTest(self): + try: + self.client.pull('joffrey/test001') + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestCommit(BaseTestCase): + def runTest(self): + try: + self.client.commit(FAKE_CONTAINER_ID) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +class TestRemoveImage(BaseTestCase): + def runTest(self): + try: + self.client.remove_image(FAKE_IMAGE_ID) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +# ################# +# # BUILDER TESTS # +# ################# + +class TestBuild(BaseTestCase): + def runTest(self): + script = StringIO('\n'.join([ + 'FROM busybox', + 'MAINTAINER docker-py', + 'RUN mkdir -p /tmp/test', + 'EXPOSE 8080', + 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz /tmp/silence.tar.gz' + ])) + try: + self.client.build(fileobj=script) + except Exception as e: + self.fail('Command should not raise exception: ' + str(e)) + + +# ####################### +# ## PY SPECIFIC TESTS ## +# ####################### + +class TestLoadConfig(BaseTestCase): + def runTest(self): + folder = tempfile.mkdtemp() + f = open(os.path.join(folder, '.dockercfg'), 'w') + auth_ = base64.b64encode('sakuya:izayoi') + f.write('auth = {0}\n'.format(auth_)) + f.write('email = sakuya@scarlet.net') + f.close() + cfg = docker.auth.load_config(folder) + self.assertNotEqual(cfg['Configs'][docker.auth.INDEX_URL], None) + cfg = cfg['Configs'][docker.auth.INDEX_URL] + self.assertEqual(cfg['Username'], 'sakuya') + self.assertEqual(cfg['Password'], 'izayoi') + self.assertEqual(cfg['Email'], 'sakuya@scarlet.net') + self.assertEqual(cfg.get('Auth'), None) + +if __name__ == '__main__': + unittest.main()