def test_collection_operations_003():
        """Test merging resources and collections.

        Verify that merging collection or resource to collection works.
        """

        collection1 = Collection()
        collection2 = Collection()
        collection1.load_dict(
            Helper.EXPORT_TIME, {
                'data': [{
                    'data': [
                        'tar cvfz mytar.tar.gz --exclude="mytar.tar.gz" ./',
                        'tar xfO mytar.tar.gz manifest.json# Cat file in compressed tar.'
                    ],
                    'brief':
                    'Manipulate compressed tar files',
                    'groups': ['linux'],
                    'tags': ['howto', 'linux', 'tar', 'untar'],
                    'category':
                    Const.SNIPPET
                }]
            })

        # Try to merge two collections that is not supported.
        assert len(collection1) == 1
        assert not collection2
        digest = collection2.merge(collection1)
        assert not collection2
        assert digest is None

        # Merge resource to collection.
        digest = collection2.merge(collection1[
            'e79ae51895908c5a40e570dc60a4dd594febdecf781c77c7b3cad37f9e0b7240']
                                   )
        assert len(collection2) == 1
        assert digest == 'e79ae51895908c5a40e570dc60a4dd594febdecf781c77c7b3cad37f9e0b7240'

        # Merge already existing resource to collection.
        digest = collection2.merge(collection1[
            'e79ae51895908c5a40e570dc60a4dd594febdecf781c77c7b3cad37f9e0b7240']
                                   )
        assert len(collection2) == 1
        assert digest == 'e79ae51895908c5a40e570dc60a4dd594febdecf781c77c7b3cad37f9e0b7240'

        # Try to migrate random type to collection.
        collection2.migrate('string')
        assert len(collection2) == 1
Example #2
0
    def get_collection(cls, updates=None):
        """Get collection of resources.

        Read collection of resources from the used configuration source. If
        resource updates are provided on top of the configured content, the
        updates are merged or migrated to the configuration.

        Args:
            updates (obj): Updates for the configured content in a Resource().

        Returns:
            obj: Configured content in a Collection().
        """

        collection = Collection()
        timestamp = Config.utcnow()
        resource = cls._get_config(timestamp,
                                   collection,
                                   updates,
                                   merge=Config.merge)
        if cls.editor:
            template = resource.get_template(cls.content_category,
                                             cls.template_format,
                                             cls.templates)
            Editor.read(timestamp, cls.template_format, template, collection)
        else:
            collection.migrate(resource)

        return collection
Example #3
0
    def run(self, collection=Collection()):
        """Run operation."""

        self._logger.debug('run: %s :content', self._category)

        Config.content_category = self._category
        self.collection = collection
        if Config.is_operation_create:
            self.create()
        elif Config.is_operation_search:
            self.search()
        elif Config.is_operation_unique:
            self.unique()
        elif Config.is_operation_update:
            self.update()
        elif Config.is_operation_delete:
            self.delete()
        elif Config.is_operation_export:
            self.export_all()
        elif Config.is_operation_import:
            self.import_all()
        else:
            Cause.push(Cause.HTTP_BAD_REQUEST, 'unknown operation for: {}'.format(self._category))

        self._logger.debug('end: %s :content', self._category)
Example #4
0
    def _select_uuid(self, suuid):
        """Select content based on uuid.

        Args:
            uuid (str): Content uuid or part of it.

        Returns:
            Collection: Collection of selected content.
        """

        collection = Collection()
        if self._connection:
            query = ('SELECT * FROM contents WHERE uuid = {0}'.format(self._placeholder))
            qargs = [suuid]
            self._logger.debug('running select uuid query: %s :with qargs: %s', query, qargs)
            try:
                with closing(self._connection.cursor()) as cursor:
                    cursor.execute(query, qargs)
                    rows = cursor.fetchall()
                    collection.convert(rows)
            except (sqlite3.DataError, psycopg2.DataError) as error:
                # This method is used only validated content which should
                # always have valid external UUID field. Because of this,
                # the error here is internal server error.
                self._connection.rollback()
                self._set_data_error(error)
                Cause.push(Cause.HTTP_500, 'invalid user data for search: {}'.format(qargs))
            except (sqlite3.Error, psycopg2.Error) as error:
                self._set_error(error)
        else:
            Cause.push(Cause.HTTP_500, 'internal error prevented searching from database')
        self._logger.debug('selected rows: %s', rows)

        return collection
Example #5
0
    def insert(self, collection):
        """Insert collection into database.

        If any of the resources in the given collection is successfully
        inseted, the operation results Created status. The failing resources
        each produce own failure cause.

        Args:
            collection (Collection): Content container to be stored.

        Returns:
            Collection: Collection of inserted content.
        """

        stored = Collection()
        if not collection:
            Cause.push(Cause.HTTP_NOT_FOUND, 'no content to be stored')

            return stored

        if self._insert(collection):
            Cause.push(Cause.HTTP_CREATED, 'content created')
            for resource in collection:
                stored.migrate(
                    self.select(resource.category, digest=resource.digest))
        self._logger.debug('inserted: %d :out of: %d :content', len(stored),
                           len(collection))

        return stored
Example #6
0
    def _select_data(self, data):
        """Select content based on data.

        Args:
            data (str): Content data or part of it.

        Returns:
            Collection: Collection of selected content.
        """

        collection = Collection()
        if self._connection:
            query = ('SELECT * FROM contents WHERE data={0}'.format(
                self._placeholder))
            qargs = [Const.DELIMITER_DATA.join(map(Const.TEXT_TYPE, data))]
            self._logger.debug('search content with data attribute: %s' %
                               qargs)
            try:
                with closing(self._connection.cursor()) as cursor:
                    cursor.execute(query, qargs)
                    rows = cursor.fetchall()
                    collection.convert(rows)
            except (sqlite3.Error, psycopg2.Error) as error:
                Cause.push(
                    Cause.HTTP_500,
                    'selecting content from database with data failed with exception: {}'
                    .format(error))
        else:
            Cause.push(Cause.HTTP_500,
                       'internal error prevented searching from database')

        self._logger.debug('selected rows:\n%s', rows)

        return collection
Example #7
0
    def dump_text(cls, content):
        """Return text from given content.

        This can be used for example to convert test case content to text
        string to be used as a response from mocked editor.

        In order to be able to insert multiple Markdown contents to database,
        the UUID must be unique. Because of this, the conversion must not use
        the methods that masks the content fields to common values. This is
        applicaple only to Markdown content which has the full metadata.

        The text string is returned from resource. The collection adds one
        extra newline in the string.

        Args:
            content (dict): Single content that is converted to text.

        Returns:
            str: Text string created from given content.
        """

        collection = Collection()
        collection.load_dict('2018-10-20T06:16:27.000001+00:00',
                             {'data': [content]})

        # Collection adds one extra newline which must be removed. The rstrip()
        # cannot be used because it would remove all the trailing newlines.
        return collection.dump_text(Config.templates)[:-1]
Example #8
0
    def select(self, scat=(), sall=(), stag=(), sgrp=(), search_filter=None, uuid=None, digest=None, identity=None, data=None):  # noqa pylint: disable=too-many-arguments,too-many-locals
        """Select content based on search criteria.

        The search filter is applied after the result is received from
        database. The search filter removes all resources from returned
        collection that do not match to the filter.

        Args:
            scat (tuple): Search category keyword list.
            sall (tuple): Search all keyword list.
            stag (tuple): Search tag keyword list.
            sgrp (tuple): Search group keyword list.
            search_filter (str): Regexp filter to limit search results.
            uuid (str): Search specific uuid or part of it.
            digest (str): Search specific digest or part of it.
            identity (str): Search specific digest or UUID or part of them.
            data (str): Search specific content data or part of it.

        Returns:
            Collection: Collection of selected content.
        """

        collection = Collection()
        query, qargs = self._get_query(None, scat, sall, stag, sgrp, uuid, digest, identity, data, Database.QUERY_TYPE_REGEX)
        if query:
            rows = self._select(query, qargs)
            self._logger.debug('selected: %d :rows: %s', len(rows), rows)
            if search_filter:
                rows = [row for row in rows if any(search_filter.search(str(column)) for column in row)]
                self._logger.debug('regexp filter applied: %s :resulting: %d :rows: %s', search_filter, len(rows), rows)
            total = self._count_content(scat, sall, stag, sgrp, uuid, digest, identity, data)
            collection.convert(rows)
            collection.total = total

        return collection
Example #9
0
    def _get_result_collection(content_format, mock_object):
        """Return comparable collection from test case result.

        See the description for assert_storage method.

        Args:
            content_format (str): Content format stored in mock.
            mock_object (obj): Mock object where content was stored.

        Returns:
            Collection(): Comparable collection from test case result.
        """

        collection = Collection()
        if content_format == Const.CONTENT_FORMAT_JSON:
            for call in mock_object.dump.mock_calls:
                collection.load_dict(Content.IMPORT_TIME, call[1][0])
        elif content_format in (Const.CONTENT_FORMAT_MKDN,
                                Const.CONTENT_FORMAT_TEXT):
            handle = mock_object.return_value.__enter__.return_value
            for call in handle.write.mock_calls:
                collection.load(content_format, Content.IMPORT_TIME,
                                call[1][0])
        elif content_format == Const.CONTENT_FORMAT_YAML:
            for call in mock_object.safe_dump.mock_calls:
                collection.load_dict(Content.IMPORT_TIME, call[1][0])

        return collection
    def test_parser_snippet_002(self):
        """Test parsing unknown content.

        In this case the content category is incorrect.
        """

        dictionary = {
            'data': [{
                'category': 'failure',
                'data': ['ls -al .'],
                'brief': ' strip spaces   ',
                'description': ' strip spaces   ',
                'name': '',
                'groups': 'default',
                'tags': 'tag2,tag1',
                'links': [],
                'source': '',
                'versions': (),
                'languages': (),
                'filename': '',
                'created': '2015-10-14T19:56:31.000001+00:00',
                'updated': '2016-10-14T19:56:31.000001+00:00',
                'uuid': '11cd5827-b6ef-4067-b5ac-3ceac07dde9f',
                'digest': '3d855210284302d58cf383ea25d8abdea2f7c61c4e2198da01e2c0896b0268dd'
            }]
        }
        collection = Collection()
        Parser(self.TIMESTAMP, dictionary, collection).read_collection()
        assert not collection
Example #11
0
    def select_all(self, scat):
        """Select all content from specific categories.

        Args:
            scat (tuple): Search category keyword list.

        Returns:
            Collection: Collection of all content in database.
        """

        collection = Collection()
        if self._connection:
            self._logger.debug('select all contents from categories: %s', scat)

            query = ('SELECT * FROM contents WHERE (')
            for _ in scat:
                query = query + 'category={0} OR '.format(self._placeholder)
            query = query[:-4]  # Remove last ' OR ' added by the loop.
            query = query + ') ORDER BY created ASC, brief ASC'
            qargs = list(scat)
            try:
                with closing(self._connection.cursor()) as cursor:
                    cursor.execute(query, qargs)
                    rows = cursor.fetchall()
                    collection.convert(rows)
            except (sqlite3.Error, psycopg2.Error) as error:
                Cause.push(Cause.HTTP_500, 'selecting all from database failed with exception: {}'.format(error))
        else:
            Cause.push(Cause.HTTP_500, 'internal error prevented selecting all content from database')

        return collection
Example #12
0
    def on_post(self, request, response, **kwargs):  # pylint: disable=unused-argument
        """Create new resource.

        Args:
            request (obj): Falcon Request().
            response (obj): Falcon Response().
        """

        self._logger.debug('run: %s %s', request.method, request.uri)
        collection = Collection()
        data = Validate.json_object(request)
        for resource in data:
            api = Api(self._category, Api.CREATE, resource)
            Config.load(api)
            self._content.run(collection)
        if Cause.is_ok():
            response.content_type = ApiResource.MEDIA_JSON_API
            response.body = Generate.collection(collection, request, response)
            response.status = Cause.http_status()
        else:
            response.content_type = ApiResource.MEDIA_JSON_API
            response.body = Generate.error(Cause.json_message())
            response.status = Cause.http_status()
        Cause.reset()
        self._logger.debug('end: %s %s', request.method, request.uri)
Example #13
0
def _get_template_mkdn(dictionary):
    """Transform dictionary to Markdown template."""

    collection = Collection()
    collection.load_dict('2018-10-20T06:16:27.000001+00:00',
                         {'data': [dictionary]})

    return collection.dump_mkdn(Config.templates)
    def test_collection_operations_004():
        """Test collection data class operations.

        Verify values with None generate default values for resource. For
        example a None for brief must generate empty string for resource
        brief attribute.
        """

        collection = Collection()
        collection.load_dict(
            Helper.EXPORT_TIME, {
                'data': [{
                    'category':
                    Const.SNIPPET,
                    'data': [
                        'tar cvfz mytar.tar.gz --exclude="mytar.tar.gz" ./',
                        'tar xfO mytar.tar.gz manifest.json# Cat file in compressed tar.'
                    ],
                    'brief':
                    None,
                    'description':
                    None,
                    'name':
                    None,
                    'groups':
                    None,
                    'tags':
                    None,
                    'links':
                    None,
                    'source':
                    None,
                    'versions':
                    None,
                    'languages':
                    None,
                    'filename':
                    None
                }]
            })
        resource = next(collection.resources())
        assert resource.category == Const.SNIPPET
        assert resource.data == (
            'tar cvfz mytar.tar.gz --exclude="mytar.tar.gz" ./',
            'tar xfO mytar.tar.gz manifest.json# Cat file in compressed tar.')
        assert resource.brief == ''
        assert resource.description == ''
        assert resource.name == ''
        assert resource.groups == ()
        assert resource.tags == ()
        assert resource.links == ()
        assert resource.source == ''
        assert resource.versions == ()
        assert resource.languages == ()
        assert resource.filename == ''
        assert resource.digest == '6dae3799010719ca694b86514ec404cd6b6047a2979b3dbaf75fa51576ad269c'
Example #15
0
    def update(self, digest, resource):
        """Update existing content.

        Args:
            digest (str): Content digest that is udpated.
            resource (Resource): Stored content in ``Resource()`` container.

        Returns:
            Collection: Collection of updated content.
        """

        stored = Collection()
        if not resource:
            Cause.push(Cause.HTTP_NOT_FOUND, 'no content to be updated')

            return stored

        query = '''
            UPDATE
                          contents
            SET           id          = {0}
                        , category    = {0}
                        , data        = {0}
                        , brief       = {0}
                        , description = {0}
                        , name        = {0}
                        , groups      = {0}
                        , tags        = {0}
                        , links       = {0}
                        , source      = {0}
                        , versions    = {0}
                        , filename    = {0}
                        , created     = {0}
                        , updated     = {0}
                        , uuid        = {0}
                        , digest      = {0}
            WHERE
                        digest LIKE     {0}
            '''.format(self._placeholder)
        qargs = resource.dump_qargs() + (digest, )
        try:
            with closing(self._connection.cursor()) as cursor:
                cursor.execute(query, qargs)
                self._connection.commit()
        except (sqlite3.IntegrityError, psycopg2.IntegrityError) as error:
            self._logger.info(
                'database integrity error with query: {}'.format(query))
            self._logger.info(
                'database integrity error with query arguments: {}'.format(
                    qargs))
            self._set_integrity_error(error, resource)
        except (sqlite3.Error, psycopg2.Error) as error:
            self._set_error(error)
        stored.migrate(self.select(resource.category, digest=resource.digest))

        return stored
    def test_parser_snippet_001(self):
        """Test parsing snippet.

        Test case verifies that standard snippet is parsed correctly from
        dictionary. In this case the input is given in list context for
        data and links. For the groups and tags the input is give as a
        string. All of these cases must result tuple.

        The string fields contain errors like additional spaces that must
        be trimmed.
        """

        dictionary = {
            'data': [{
                'category': 'snippet',
                'data': ['docker rm $(docker ps --all -q -f status=exited)'],
                'brief': ' strip spaces   ',
                'description': ' strip spaces   ',
                'name': '',
                'groups': 'default',
                'tags': 'tag2,tag1',
                'links': [],
                'source': '',
                'versions': [],
                'languages': [],
                'filename': '',
                'created': '2015-10-14T19:56:31.000001+00:00',
                'updated': '2016-10-14T19:56:31.000001+00:00',
                'uuid': '11cd5827-b6ef-4067-b5ac-3ceac07dde9f',
                'digest': '3d855210284302d58cf383ea25d8abdea2f7c61c4e2198da01e2c0896b0268dd'
            }]
        }
        collection = Collection()
        Parser(self.TIMESTAMP, dictionary, collection).read_collection()
        resource = next(collection.resources())
        assert resource.category == Const.SNIPPET
        assert resource.data == ('docker rm $(docker ps --all -q -f status=exited)',)
        assert resource.brief == 'strip spaces'
        assert resource.description == 'strip spaces'
        assert resource.name == ''
        assert resource.groups == ('default',)
        assert resource.tags == ('tag1', 'tag2')
        assert resource.links == ()
        assert resource.source == ''
        assert resource.versions == ()
        assert resource.languages == ()
        assert resource.filename == ''
        assert resource.created == '2015-10-14T19:56:31.000001+00:00'
        assert resource.updated == '2016-10-14T19:56:31.000001+00:00'
        assert resource.uuid == '11cd5827-b6ef-4067-b5ac-3ceac07dde9f'
        assert resource.digest == '76257166ef4499ffbbf4036accd161184e9b91f326b0b6f3d5e7a1333b516713'
Example #17
0
    def dump_dict(content):
        """Return content in dictionary format.

        Args:
            content (str): Content in text string format.

        Returns:
            dict: Content in dictionary format.
        """

        collection = Collection()
        collection.load_text(Content.IMPORT_TIME, content)

        return collection.dump_dict()[0]
Example #18
0
    def get_collection(content):
        """Return collection from content.

        Args:
            content (dict): Content in a dictionary format.

        Returns:
            CollectionI(): Content stored in collection.
        """

        collection = Collection()
        collection.load(Const.CONTENT_FORMAT_DICT, Content.IMPORT_TIME,
                        {'data': [content]})

        return collection
Example #19
0
    def _get_expect_collection(content):
        """Return comparable collection from expected content.

        See the description for assert_storage method.

        Args:
            content (dict): Reference content.

        Returns:
            Collection(): Comparable collection from expected content.
        """

        references = Collection()
        references.load_dict(Content.IMPORT_TIME, {'data': content['data']})

        return references
Example #20
0
    def dump_mkdn(cls, content):
        """Return Markdown from given content.

        See dump_text.

        Args:
            content (dict): Single content that is converted to Markdown.

        Returns:
            str: Text string in Markdown format created from given content.
        """

        collection = Collection()
        collection.load_dict('2018-10-20T06:16:27.000001+00:00',
                             {'data': [content]})

        return collection.dump_mkdn(Config.templates)
Example #21
0
    def _select(cls, category):
        """Return content based on category."""

        rows = ()
        collection = Collection()
        try:
            query = ('SELECT * FROM contents WHERE category={0} ORDER BY created ASC, brief ASC'.format(cls._PLACEHOLDER))
            qargs = [category]
            connection = Database._connect()
            with closing(connection.cursor()) as cursor:
                cursor.execute(query, qargs)
                rows = cursor.fetchall()
            connection.close()
        except (sqlite3.Error, psycopg2.Error):
            print(traceback.format_exc())

        collection.convert(rows)

        return collection
Example #22
0
    def dump_template(cls, category):
        """Dump content template into file."""

        filename = Config.get_operation_file()
        resource = Collection.get_resource(category, Config.utcnow())
        template = resource.get_template(category, Config.template_format,
                                         Config.templates)
        cls._logger.debug('exporting content template %s', filename)
        with open(filename, 'w') as outfile:
            try:
                outfile.write(template)
            except IOError as error:
                cls._logger.exception(
                    'fatal failure in creating %s template file "%s"',
                    category, error)
                Cause.push(
                    Cause.HTTP_INTERNAL_SERVER_ERROR,
                    'fatal failure while exporting template {}'.format(
                        filename))
Example #23
0
    def get_collection():
        """Return database rows as collection.

        This method may be called before the database is created. Because of
        this, the exception is silently discarded here.
        """

        rows = ()
        collection = Collection()
        try:
            connection = Database._connect()
            with closing(connection.cursor()) as cursor:
                cursor.execute('SELECT * FROM contents ORDER BY created ASC, brief ASC')
                rows = cursor.fetchall()
            connection.close()
            collection.convert(rows)
        except (sqlite3.Error, psycopg2.Error) as error:
            print('database helper select exception: {}'.format(error))

        return collection
Example #24
0
    def load(cls, filename):
        """Load dictionary from file."""

        collection = Collection()
        if not Config.is_supported_file_format():
            cls._logger.debug('file format not supported for file %s',
                              filename)

            return collection

        cls._logger.debug('importing contents from file %s', filename)
        if os.path.isfile(filename):
            with open(filename, 'r') as infile:
                try:
                    timestamp = Config.utcnow()
                    if Config.is_operation_file_text:
                        collection.load_text(timestamp, infile.read())
                    elif Config.is_operation_file_mkdn:
                        collection.load_mkdn(timestamp, infile.read())
                    elif Config.is_operation_file_json:
                        dictionary = json.load(infile)
                        collection.load_dict(timestamp, dictionary)
                    elif Config.is_operation_file_yaml:
                        dictionary = yaml.safe_load(infile)
                        collection.load_dict(timestamp, dictionary)
                    else:
                        cls._logger.debug('unknown import file format')
                except (TypeError, ValueError, yaml.YAMLError) as error:
                    cls._logger.exception(
                        'fatal exception while loading file "%s"', error)
                    Cause.push(
                        Cause.HTTP_INTERNAL_SERVER_ERROR,
                        'fatal failure while importing content from file')

        else:
            Cause.push(Cause.HTTP_NOT_FOUND,
                       'cannot read file {}'.format(filename))

        return collection
    def test_collection_operations_001(capsys):
        """Test collection data class operations.

        Verify that collection class implements data class methods correctly.
        In this case there are no resources in collection.
        """

        collection = Collection()

        # Collection with len().
        assert not collection

        # Collection with condition.
        if collection:
            assert 0
        else:
            assert 1

        # Collection with negative condition.
        if not collection:
            assert 1
        else:
            assert 0

        # Equality of two empty collections.
        collection2 = Collection()
        if collection == collection2:
            assert 1
        else:
            assert 0

        # Non equality of two empty collections.
        if collection != collection2:
            assert 0
        else:
            assert 1

        # Iterate resources in collection.
        for resource in collection:
            resource.digest = resource.digest
            assert 0

        # Get list of keys (digest) from collection.
        assert not collection.keys()

        # Get list of values (resources) from collection.
        assert not collection.values()

        # Test generator.
        resources = collection.resources()
        with pytest.raises(StopIteration):
            next(resources)

        # Printing collection.
        output = ('# collection meta', '   ! total : 0', '', '')
        print(collection)  # Part of the test.
        out, err = capsys.readouterr()
        out = Helper.remove_ansi(out)
        assert out == Const.NEWLINE.join(output)
        assert not err

        # Access non existent resource from collection.
        with pytest.raises(KeyError):
            resource = collection[0]

        # Delete non existent resource from collection with string.
        with pytest.raises(KeyError):
            del collection['012123']

        # Delete non existent resource from collection with number.
        with pytest.raises(KeyError):
            del collection[0]

        # Two created objects must not point ot same reference.
        if collection is collection2:
            assert 0

        # Reference of object must be to the same object.
        collection3 = collection
        if collection3 is not collection:
            assert 0
    def test_collection_operations_002(capsys):  # pylint: disable=too-many-branches
        """Test collection data class operations.

        Verify that collection class implements data class methods correctly.
        In this case there is only one resource in collection.
        """

        collection = Collection()
        collection.load_dict(
            Helper.EXPORT_TIME, {
                'data': [{
                    'data': [
                        'tar cvfz mytar.tar.gz --exclude="mytar.tar.gz" ./',
                        'tar xfO mytar.tar.gz manifest.json# Cat file in compressed tar.'
                    ],
                    'brief':
                    'Manipulate compressed tar files',
                    'groups': ['linux'],
                    'tags': ['howto', 'linux', 'tar', 'untar'],
                    'category':
                    Const.SNIPPET
                }]
            })

        # Collection with len().
        assert len(collection) == 1

        # Collection with condition.
        if collection:
            assert 1
        else:
            assert 0

        # Collection with negative condition.
        if not collection:
            assert 0
        else:
            assert 1

        # Equality of two different collections where the UUID differs.
        collection2 = Collection()
        collection2.load_dict(
            Helper.EXPORT_TIME, {
                'data': [{
                    'data': [
                        'tar cvfz mytar.tar.gz --exclude="mytar.tar.gz" ./',
                        'tar xfO mytar.tar.gz manifest.json# Cat file in compressed tar.'
                    ],
                    'brief':
                    'Manipulate compressed tar files',
                    'groups': ['linux'],
                    'tags': ['howto', 'linux', 'tar', 'untar'],
                    'category':
                    Const.SNIPPET
                }]
            })
        if collection == collection2:
            assert 0
        else:
            assert 1

        # Non equality of two different collections.
        if collection != collection2:
            assert 1
        else:
            assert 0

        # Equality of two same collections.
        collection2 = collection
        if collection == collection2:
            assert 1
        else:
            assert 0

        # Non equality of same collections.
        if collection != collection2:
            assert 0
        else:
            assert 1

        # Equality of two collection with different length.
        collection2 = Collection()
        if collection == collection2:
            assert 0
        else:
            assert 1

        # Equality collection and random type.
        if collection == 1:
            assert 0
        else:
            assert 1

        # Iterate resources in collection.
        i = 0
        for resource in collection:
            resource.digest = resource.digest
            i = i + 1
        assert i == 1

        # Get list of keys (digest) from collection.
        assert len(collection.keys()) == 1
        assert collection.keys() == list([
            'e79ae51895908c5a40e570dc60a4dd594febdecf781c77c7b3cad37f9e0b7240'
        ])

        # Get list of values (resources) from collection.
        assert len(collection.values()) == 1
        assert collection.values()[0] == collection[
            'e79ae51895908c5a40e570dc60a4dd594febdecf781c77c7b3cad37f9e0b7240']

        # Test generator.
        resources = collection.resources()
        assert next(resources) == collection[
            'e79ae51895908c5a40e570dc60a4dd594febdecf781c77c7b3cad37f9e0b7240']
        with pytest.raises(StopIteration):
            next(resources)

        # Printing collection.
        output = (
            '1. Manipulate compressed tar files @linux [e79ae51895908c5a]', '',
            '   $ tar cvfz mytar.tar.gz --exclude="mytar.tar.gz" ./',
            '   $ tar xfO mytar.tar.gz manifest.json# Cat file in compressed tar.',
            '', '   # howto,linux,tar,untar', '', '   ! category    : snippet',
            '   ! created     : 2018-02-02T02:02:02.000001+00:00',
            '   ! description : ',
            '   ! digest      : e79ae51895908c5a40e570dc60a4dd594febdecf781c77c7b3cad37f9e0b7240 (True)',
            '   ! filename    : ',
            '   ! id          : a1cd5827-b6ef-4067-b5ac-3ceac07dde9f',
            '   ! languages   : ', '   ! name        : ',
            '   ! source      : ',
            '   ! updated     : 2018-02-02T02:02:02.000001+00:00',
            '   ! uuid        : a1cd5827-b6ef-4067-b5ac-3ceac07dde9f',
            '   ! versions    : ', '', '# collection meta', '   ! total : 1',
            '', '')
        print(collection)  # Part of the test.
        out, err = capsys.readouterr()
        out = Helper.remove_ansi(out)
        assert out == Const.NEWLINE.join(output)
        assert not err

        with pytest.raises(KeyError):
            resource = collection[0]