from openshift.dynamic.apply import merge, apply_patch

tests = [
    dict(
        last_applied = dict(
            kind="ConfigMap",
            metadata=dict(name="foo"),
            data=dict(one="1", two="2")
        ),
        desired = dict(
            kind="ConfigMap",
            metadata=dict(name="foo"),
            data=dict(one="1", two="2")
        ),
        expected = {}
    ),
    dict(
        last_applied = dict(
            kind="ConfigMap",
            metadata=dict(name="foo"),
            data=dict(one="1", two="2")
        ),
        desired = dict(
            kind="ConfigMap",
            metadata=dict(name="foo"),
            data=dict(one="1", two="2", three="3")
        ),
        expected = dict(data=dict(three="3"))
    ),
    dict(
        last_applied = dict(
            kind="ConfigMap",
            metadata=dict(name="foo"),
            data=dict(one="1", two="2")
        ),
        desired = dict(
            kind="ConfigMap",
            metadata=dict(name="foo"),
            data=dict(one="1", three="3")
        ),
        expected = dict(data=dict(two=None, three="3"))
    ),
    dict(
        last_applied = dict(
            kind="ConfigMap",
            metadata=dict(name="foo", annotations=dict(this="one", hello="world")),
            data=dict(one="1", two="2")
        ),
        desired = dict(
            kind="ConfigMap",
            metadata=dict(name="foo"),
            data=dict(one="1", three="3")
        ),
        expected = dict(metadata=dict(annotations=None), data=dict(two=None, three="3"))
    ),

    dict(
        last_applied = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8080, name="http")])
        ),
        actual = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8080, protocol='TCP', name="http")])
        ),
        desired = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8080, name="http")])
        ),
        expected = dict(spec=dict(ports=[dict(port=8080, protocol='TCP', name="http")]))
    ),
    dict(
        last_applied = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8080, name="http")])
        ),
        actual = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8080, protocol='TCP', name="http")])
        ),
        desired = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8081, name="http")])
        ),
        expected = dict(spec=dict(ports=[dict(port=8081, name="http")]))
    ),
    dict(
        last_applied = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8080, name="http")])
        ),
        actual = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8080, protocol='TCP', name="http")])
        ),
        desired = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8443, name="https"), dict(port=8080, name="http")])
        ),
        expected = dict(spec=dict(ports=[dict(port=8443, name="https"), dict(port=8080, name="http", protocol='TCP')]))
    ),
    dict(
        last_applied = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8443, name="https"), dict(port=8080, name="http")])
        ),
        actual = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8443, protocol='TCP', name="https"), dict(port=8080, protocol='TCP', name='http')])
        ),
        desired = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8080, name="http")])
        ),
        expected = dict(spec=dict(ports=[dict(port=8080, name="http", protocol='TCP')]))
    ),
    dict(
        last_applied = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8443, name="https", madeup="xyz"), dict(port=8080, name="http")])
        ),
        actual = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8443, protocol='TCP', name="https", madeup="xyz"), dict(port=8080, protocol='TCP', name='http')])
        ),
        desired = dict(
            kind="Service",
            metadata=dict(name="foo"),
            spec=dict(ports=[dict(port=8443, name="https")])
        ),
        expected = dict(spec=dict(ports=[dict(madeup=None, port=8443, name="https", protocol='TCP')]))
    ),
    dict(
        last_applied = dict(
            kind="Pod",
            metadata=dict(name="foo"),
            spec=dict(containers=[dict(name="busybox", image="busybox",
                                       resources=dict(requests=dict(cpu="100m", memory="100Mi"), limits=dict(cpu="100m", memory="100Mi")))])
        ),
        actual = dict(
            kind="Pod",
            metadata=dict(name="foo"),
            spec=dict(containers=[dict(name="busybox", image="busybox",
                                       resources=dict(requests=dict(cpu="100m", memory="100Mi"), limits=dict(cpu="100m", memory="100Mi")))])
        ),
        desired = dict(
            kind="Pod",
            metadata=dict(name="foo"),
            spec=dict(containers=[dict(name="busybox", image="busybox",
                                       resources=dict(requests=dict(cpu="50m", memory="50Mi"), limits=dict(memory="50Mi")))])
        ),
        expected=dict(spec=dict(containers=[dict(name="busybox", image="busybox",
                                                 resources=dict(requests=dict(cpu="50m", memory="50Mi"), limits=dict(cpu=None, memory="50Mi")))]))
    ),
    dict(
        desired = dict(kind='Pod',
                       spec=dict(containers=[
                           dict(name='hello',
                                volumeMounts=[dict(name="test", mountPath="/test")])
                           ],
                           volumes=[
                               dict(name="test", configMap=dict(name="test")),
                           ])),
        last_applied = dict(kind='Pod',
                       spec=dict(containers=[
                           dict(name='hello',
                                volumeMounts=[dict(name="test", mountPath="/test")])
                           ],
                           volumes=[
                               dict(name="test", configMap=dict(name="test")),
                           ])),
        actual = dict(kind='Pod',
                       spec=dict(containers=[
                           dict(name='hello',
                                volumeMounts=[dict(name="test", mountPath="/test"),
                                              dict(mountPath="/var/run/secrets/kubernetes.io/serviceaccount", name="default-token-xyz")])
                           ],
                           volumes=[
                               dict(name="test", configMap=dict(name="test")),
                               dict(name="default-token-xyz", secret=dict(secretName="default-token-xyz")),
                           ])),
        expected = dict(spec=dict(containers=[
                           dict(name='hello',
                                volumeMounts=[dict(name="test", mountPath="/test"),
                                              dict(mountPath="/var/run/secrets/kubernetes.io/serviceaccount", name="default-token-xyz")])
                           ],
                           volumes=[
                               dict(name="test", configMap=dict(name="test")),
                               dict(name="default-token-xyz", secret=dict(secretName="default-token-xyz")),
                           ])),
    ),

    # This next one is based on a real world case where definition was mostly
    # str type and everything else was mostly unicode type (don't ask me how)
    dict(
        last_applied = {
            u'kind': u'ConfigMap',
            u'data': {u'one': '1', 'three': '3', 'two': '2'},
            u'apiVersion': u'v1',
            u'metadata': {u'namespace': u'apply', u'name': u'apply-configmap'}
        },
        actual = {
            u'kind': u'ConfigMap',
            u'data': {u'one': '1', 'three': '3', 'two': '2'},
            u'apiVersion': u'v1',
            u'metadata': {u'namespace': u'apply', u'name': u'apply-configmap',
                          u'resourceVersion': '1714994',
                          u'creationTimestamp': u'2019-08-17T05:08:05Z', u'annotations': {},
                          u'selfLink': u'/api/v1/namespaces/apply/configmaps/apply-configmap',
                          u'uid': u'fed45fb0-c0ac-11e9-9d95-025000000001'}
        },
        desired = {
            'kind': u'ConfigMap',
            'data': {'one': '1', 'three': '3', 'two': '2'},
            'apiVersion': 'v1',
            'metadata': {'namespace': 'apply', 'name': 'apply-configmap'}
        },
        expected = dict()
    ),
    # apply a Deployment, then scale the Deployment (which doesn't affect last-applied)
    # then apply the Deployment again. Should un-scale the Deployment
    dict(
        last_applied = {
            'kind': u'Deployment',
            'spec': {
                'replicas': 1,
                'template': {
                    'spec': {
                        'containers': [
                            {
                                'name': 'this_must_exist',
                                'envFrom': [
                                    {
                                        'configMapRef': {
                                            'name': 'config-xyz'
                                        }
                                    },
                                    {
                                        'secretRef': {
                                            'name': 'config-wxy'
                                        }
                                    }
                                ]
                            }
                        ]
                    }
                }
            },
            'metadata': {
                'namespace': 'apply',
                'name': u'apply-deployment'
            }
        },
        actual = {
            'kind': u'Deployment',
            'spec': {
                'replicas': 0,
                'template': {
                    'spec': {
                        'containers': [
                            {
                                'name': 'this_must_exist',
                                'envFrom': [
                                    {
                                        'configMapRef': {
                                            'name': 'config-xyz'
                                        }
                                    },
                                    {
                                        'secretRef': {
                                            'name': 'config-wxy'
                                        }
                                    }
                                ]
                            }
                        ]
                    }
                }
            },
            'metadata': {
                'namespace': 'apply',
                'name': u'apply-deployment'
            }
        },
        desired = {
            'kind': u'Deployment',
            'spec': {
                'replicas': 1,
                'template': {
                    'spec': {
                        'containers': [
                            {
                                'name': 'this_must_exist',
                                'envFrom': [
                                    {
                                        'configMapRef': {
                                            'name': 'config-abc'
                                        }
                                    }
                                ]
                            }
                        ]
                    }
                }
            },
            'metadata': {
                'namespace': 'apply',
                'name': u'apply-deployment'
            }
        },
        expected = {
            'spec' : {
                'replicas': 1,
                'template': {
                    'spec': {
                        'containers': [
                            {
                                'name': 'this_must_exist',
                                'envFrom': [
                                    {
                                        'configMapRef': {
                                            'name': 'config-abc'
                                        }
                                    }
                                ]
                            }
                        ]
                    }
                }
            }
        }
    ),
    dict(
        last_applied = {
            'kind': 'MadeUp',
            'toplevel': {
                'original': 'entry'
            }
        },
        actual = {
            'kind': 'MadeUp',
            'toplevel': {
                'original': 'entry',
                'another': {
                    'nested': {
                         'entry': 'value'
                    }
                }
            }
        },
        desired = {
            'kind': 'MadeUp',
            'toplevel': {
                'original': 'entry',
                'another': {
                    'nested': {
                         'entry': 'value'
                    }
                }
            }
        },
        expected = {}
    )
]


def test_merges():
    for test in tests:
        assert(merge(test['last_applied'], test['desired'], test.get('actual', test['last_applied'])) == test['expected'])


def test_apply_patch():
        actual = dict(
            kind="ConfigMap",
            metadata=dict(name="foo",
                          annotations={'kubectl.kubernetes.io/last-applied-configuration':
                                       '{"data":{"one":"1","two":"2"},"kind":"ConfigMap",'
                                       '"metadata":{"annotations":{"hello":"world","this":"one"},"name":"foo"}}',
                                       'this': 'one', 'hello': 'world'}),
            data=dict(one="1", two="2")
        )
        desired = dict(
            kind="ConfigMap",
            metadata=dict(name="foo"),
            data=dict(one="1", three="3")
        )
        expected = dict(
            metadata=dict(
                annotations={'kubectl.kubernetes.io/last-applied-configuration': '{"data":{"one":"1","three":"3"},"kind":"ConfigMap","metadata":{"name":"foo"}}',
                             'this': None, 'hello': None}),
                data=dict(two=None, three="3"))
        assert(apply_patch(actual, desired) == (actual, expected))
