# -*- coding: utf-8 -*-
#
# test_linux - tests for ubuntu_kylin_sso.keyring under linux
#
# Author: Alejandro J. Cura <alecu@canonical.com>
# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
#
# Copyright 2010-2012 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the
# OpenSSL library under certain conditions as described in each
# individual source file, and distribute linked combinations
# including the two.
# You must obey the GNU General Public License in all respects
# for all of the code used other than OpenSSL.  If you modify
# file(s) with this exception, you may extend this exception to your
# version of the file(s), but you are not obligated to do so.  If you
# do not wish to do so, delete this exception statement from your
# version.  If you delete this exception statement from all source
# files in the program, then also delete it here.
"""Tests for the keyring.py module."""

from __future__ import unicode_literals

from twisted.internet import defer
from twisted.internet.defer import inlineCallbacks
from twisted.trial.unittest import TestCase

from ubuntu_kylin_sso import keyring as common_keyring
from ubuntu_kylin_sso.keyring import linux as keyring


class MockItem(object):
    """An item contains a secret, lookup attributes and has a label."""

    def __init__(self, label, collection, attr, value):
        """Initialize a new Item."""
        self.label = label
        self.collection = collection
        self.attributes = attr
        self.value = value

    def get_value(self):
        """Retrieve the secret for this item."""
        return defer.succeed(self.value)

    def delete(self):
        """Delete this item."""
        self.collection.items.remove(self)
        return defer.succeed(None)

    def matches(self, search_attr):
        """See if this item matches a given search."""
        for k, val in search_attr.items():
            if k not in self.attributes:
                return False
            if self.attributes[k] != val:
                return False
        return True


class MockCollection(object):
    """A collection of items containing secrets."""

    def __init__(self, label, service):
        """Initialize a new collection."""
        self.label = label
        self.service = service
        self.items = []

    def create_item(self, label, attr, value, replace=True):
        """Create an item with the given attributes, secret and label."""
        item = MockItem(label, self, attr, value)
        self.items.append(item)
        return defer.succeed(item)


class MockSecretService(object):
    """A class that mocks txsecrets.SecretService."""

    def __init__(self, *args, **kwargs):
        super(MockSecretService, self).__init__(*args, **kwargs)
        self.collections = {}

    def open_session(self, window_id=0):
        """Open a unique session for the caller application."""
        return defer.succeed(self)

    def search_items(self, attributes):
        """Find items in any collection."""
        results = []
        for collection in self.collections.values():
            for item in collection.items:
                if item.matches(attributes):
                    results.append(item)
        return defer.succeed(results)

    def create_collection(self, label):
        """Create a new collection with the specified properties."""
        collection = MockCollection(label, self)
        self.collections[label] = collection
        if "default" not in self.collections:
            self.collections["default"] = collection
        return defer.succeed(collection)

    def get_default_collection(self):
        """The collection were default items should be created."""
        if len(self.collections) == 0:
            self.create_collection("default")
        return defer.succeed(self.collections["default"])


class TestKeyring(TestCase):
    """Test the keyring related functions."""

    timeout = 5

    @defer.inlineCallbacks
    def setUp(self):
        """Initialize the mock used in these tests."""
        yield super(TestKeyring, self).setUp()
        self.mock_service = None
        self.service = self.patch(keyring, "SecretService",
                                  self.get_mock_service)
        self.patch(common_keyring, "gethostname", lambda: "darkstar")

    def get_mock_service(self):
        """Create only one instance of the mock service per test."""
        if self.mock_service is None:
            self.mock_service = MockSecretService()
        return self.mock_service

    @inlineCallbacks
    def test_set_credentials(self):
        """Test that the set method does not erase previous keys."""
        sample_creds = {"name": "sample creds name"}
        sample_creds2 = {"name": "sample creds name 2"}
        kr = keyring.Keyring()
        yield kr.set_credentials("appname", sample_creds)
        yield kr.set_credentials("appname", sample_creds2)

        # pylint: disable=E1101
        self.assertEqual(len(kr.service.collections["default"].items), 2)

    @inlineCallbacks
    def test_delete_credentials(self):
        """Test that a given key is deleted."""
        sample_creds = {"name": "sample creds name"}
        kr = keyring.Keyring()
        yield kr.set_credentials("appname", sample_creds)
        yield kr.delete_credentials("appname")

        # pylint: disable=E1101
        self.assertEqual(len(kr.service.collections["default"].items), 1)

    @inlineCallbacks
    def test_get_credentials(self):
        """Test that credentials are properly retrieved."""
        sample_creds = {"name": "sample creds name"}
        kr = keyring.Keyring()
        yield kr.set_credentials("appname", sample_creds)

        result = yield kr.get_credentials("appname")
        self.assertEqual(result, sample_creds)

    @inlineCallbacks
    def test_get_credentials_migrating_token(self):
        """Test that credentials are properly retrieved and migrated."""
        sample_creds = {"name": "sample creds name"}
        kr = keyring.Keyring()
        self.patch(keyring, "get_token_name", keyring.get_old_token_name)
        yield kr.set_credentials("app name", sample_creds)

        result = yield kr.get_credentials("app name")
        self.assertEqual(result, sample_creds)

    @inlineCallbacks
    def test_get_old_cred_found(self):
        """The method returns a new set of creds if old creds are found."""
        sample_oauth_token = "sample oauth token"
        sample_oauth_secret = "sample oauth secret"
        old_creds = {
            "oauth_token": sample_oauth_token,
            "oauth_token_secret": sample_oauth_secret,
        }
        u1kr = common_keyring.UbuntuOneOAuthKeyring()
        yield u1kr.set_credentials(keyring.U1_APP_NAME, old_creds)

        kr = keyring.Keyring()
        result = yield kr.get_credentials(keyring.U1_APP_NAME)
        self.assertIn("token", result)
        self.assertEqual(result["token"], sample_oauth_token)
        self.assertIn("token_secret", result)
        self.assertEqual(result["token_secret"], sample_oauth_secret)

    @inlineCallbacks
    def test_get_old_cred_found_but_not_asked_for(self):
        """Returns None if old creds are present but the appname is not U1"""
        sample_oauth_token = "sample oauth token"
        sample_oauth_secret = "sample oauth secret"
        old_creds = {
            "oauth_token": sample_oauth_token,
            "oauth_token_secret": sample_oauth_secret,
        }
        u1kr = common_keyring.UbuntuOneOAuthKeyring()
        yield u1kr.set_credentials(keyring.U1_APP_NAME, old_creds)

        kr = keyring.Keyring()
        result = yield kr.get_credentials("Software Center")
        self.assertEqual(result, None)

    @inlineCallbacks
    def test_get_old_cred_not_found(self):
        """The method returns None if no old nor new credentials found."""
        kr = keyring.Keyring()
        result = yield kr.get_credentials(keyring.U1_APP_NAME)
        self.assertEqual(result, None)
