"""
    Copyright 2016 Inmanta

    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.

    Contact: bart@inmanta.com
"""
import uuid
import datetime
import time

from inmanta import data
from inmanta import const
import pytest
import pymongo
import logging


class Doc(data.BaseDocument):
    name = data.Field(field_type=str, required=True)
    field1 = data.Field(field_type=str, default=None)
    field2 = data.Field(field_type=bool, default=False)
    field3 = data.Field(field_type=list, default=[])
    field4 = data.Field(field_type=dict, default={})

    __indexes__ = [
        dict(keys=[("name", pymongo.ASCENDING)], unique=True)
    ]


class EnumDoc(data.BaseDocument):
    action = data.Field(field_type=const.ResourceAction, required=True)


@pytest.mark.asyncio
async def test_motor(motor):
    await motor.testCollection.insert_one({"a": 1, "b": "abcd"})
    results = await motor.testCollection.find_one({"a": {"$gt": 0}})

    assert "_id" in results

    await motor.testCollection.insert_one({"a": {"b": {"c": 1}}})
    results = motor.testCollection.find({})

    while (await results.fetch_next):
        assert "a" in results.next_object()


def test_collection_naming():
    assert Doc.collection_name() == "Doc"


def test_ctor():
    # Mongo data should not contain an id
    with pytest.raises(AttributeError):
        Doc(from_mongo=True, id="1234")

    # ID is autogenerated for new objects
    with pytest.raises(AttributeError):
        Doc(from_mongo=True, id="1234")

    # Required attributes have to be set in the ctor
    with pytest.raises(AttributeError):
        Doc()

    # Only defined fields are allowed
    with pytest.raises(AttributeError):
        Doc(test="")

    # A required field cannot be none
    with pytest.raises(TypeError):
        Doc(name=None)

    # Fields require the correct type
    with pytest.raises(TypeError):
        Doc(name=1234)


def test_document_def():
    t = Doc(name="doc")
    with pytest.raises(TypeError):
        t.id = "1234"

    t.id = uuid.uuid4()
    json = t.to_dict()

    assert "id" in json
    assert "_id" in t.to_mongo()


@pytest.mark.asyncio
async def test_document_insert(motor):
    Doc.set_connection(motor)

    d = Doc(name="test")

    await d.insert()
    docs = await motor.Doc.find({}).to_list(length=10)
    assert len(docs) == 1

    doc = await Doc.get_by_id(d.id)
    assert doc.name == d.name


@pytest.mark.asyncio
async def test_get_by_id(motor):
    Doc.set_connection(motor)

    d = Doc(name="test")
    await d.insert()

    d1 = await Doc.get_by_id(d.id)
    assert d1.name == d.name

    d2 = await Doc.get_by_id(uuid.uuid4())
    assert d2 is None


@pytest.mark.asyncio
async def test_defaults(motor):
    Doc.set_connection(motor)

    d = Doc(name="test")
    await d.insert()

    assert d.to_dict()["field1"] is None
    assert not d.field2

    d2 = await Doc.get_by_id(d.id)
    d2.insert()

    assert not d2.field2

    d.field3.append(1)


@pytest.mark.asyncio
async def test_document_update(motor):
    Doc.set_connection(motor)

    d = Doc(name="test")
    await d.insert()

    await d.update(name="test2")
    result = await motor.Doc.find_one({"name": "test2"})
    assert "name" in result


@pytest.mark.asyncio
async def test_document_delete(motor):
    Doc.set_connection(motor)

    d = Doc(name="test")
    await d.insert()
    await Doc.delete_all(name="test")

    docs = await Doc.get_list()
    assert len(docs) == 0


@pytest.mark.asyncio
async def test_key_escape(motor):
    Doc.set_connection(motor)

    d = Doc(name="test")
    d.field4 = {"a.b": "1", "a$b": "2", "a\\b": "3", "a.b.c": 4}
    await d.insert()

    d2 = await Doc.get_by_id(d.id)

    for k in d.field4.keys():
        assert k in d2.field4


@pytest.mark.asyncio
async def test_nested_key_escape(motor):
    Doc.set_connection(motor)

    d = Doc(name="test")
    d.field4["a0a7f22b-bc71-51f6-bc91-f2b0e368b786"] = {}
    d.field4["a0a7f22b-bc71-51f6-bc91-f2b0e368b786"]["changes"] = {}
    d.field4["a0a7f22b-bc71-51f6-bc91-f2b0e368b786"]["changes"]["routes"] = {}
    d.field4["a0a7f22b-bc71-51f6-bc91-f2b0e368b786"]["changes"]["routes"]["current"] = {}
    d.field4["a0a7f22b-bc71-51f6-bc91-f2b0e368b786"]["changes"]["routes"]["current"]["172.19.0.0/16"] = "1.1.1.1"
    await d.insert()

    await Doc.get_by_id(d.id)


@pytest.mark.asyncio
async def test_enum_field(motor):
    EnumDoc.set_connection(motor)

    d = EnumDoc(action=const.ResourceAction.deploy)
    await d.insert()

    new = await EnumDoc.get_by_id(d.id)

    assert new.action is const.ResourceAction.deploy
    assert new.to_dict()["action"] == const.ResourceAction.deploy


@pytest.mark.asyncio
async def test_project(data_module):
    project = data.Project(name="test")
    await project.insert()

    projects = await data.Project.get_list(name="test")
    assert len(projects) == 1
    assert projects[0].id == project.id

    other = await data.Project.get_by_id(project.id)
    assert project != other
    assert project.id == other.id


@pytest.mark.asyncio
async def test_project_unique(data_module):
    project = data.Project(name="test")
    await project.insert()

    project = data.Project(name="test")
    with pytest.raises(pymongo.errors.DuplicateKeyError):
        await project.insert()


@pytest.mark.asyncio
async def test_project_no_project_name(data_module):
    print(data_module)
    with pytest.raises(AttributeError):
        data.Project()


@pytest.mark.asyncio
async def test_project_cascade_delete(data_module):

    async def create_full_environment(project_name, environment_name):
        project = data.Project(name=project_name)
        await project.insert()

        env = data.Environment(name=environment_name, project=project.id, repo_url="", repo_branch="")
        await env.insert()

        agent_proc = data.AgentProcess(hostname="testhost",
                                       environment=env.id,
                                       first_seen=datetime.datetime.now(),
                                       last_seen=datetime.datetime.now(),
                                       sid=uuid.uuid4())
        await agent_proc.insert()

        agi1 = data.AgentInstance(process=agent_proc.id, name="agi1", tid=env.id)
        await agi1.insert()
        agi2 = data.AgentInstance(process=agent_proc.id, name="agi2", tid=env.id)
        await agi2.insert()

        agent = data.Agent(environment=env.id, name="agi1", last_failover=datetime.datetime.now(), paused=False,
                           primary=agi1.id)
        await agent.insert()

        version = int(time.time())
        cm = data.ConfigurationModel(version=version, environment=env.id)
        await cm.insert()

        resource_ids = []
        for i in range(5):
            path = "/etc/file" + str(i)
            key = "std::File[agent1,path=" + path + "]"
            res1 = data.Resource.new(environment=env.id, resource_version_id=key + ",v=%d" % version,
                                     attributes={"path": path})
            await res1.insert()
            resource_ids.append(res1.id)

        code = data.Code(version=version, resource="std::File", environment=env.id)
        await code.insert()

        unknown_parameter = data.UnknownParameter(name="test", environment=env.id, version=version, source="")
        await unknown_parameter.insert()

        return project, env, agent_proc, [agi1, agi2], agent, resource_ids, code, unknown_parameter

    async def assert_project_exists(project, env, agent_proc, agent_instances, agent, resource_ids, code, unknown_parameter,
                                    exists):
        def func(x):
            if exists:
                return x is not None
            else:
                return x is None
        assert func(await data.Project.get_by_id(project.id))
        assert func(await data.Environment.get_by_id(env.id))
        assert func(await data.AgentProcess.get_by_id(agent_proc.id))
        assert func(await data.AgentInstance.get_by_id(agent_instances[0].id))
        assert func(await data.AgentInstance.get_by_id(agent_instances[1].id))
        assert func(await data.Agent.get_by_id(agent.id))
        for current_id in resource_ids:
            assert func(await data.Resource.get_by_id(current_id))
        assert func(await data.Code.get_by_id(code.id))
        assert func(await data.UnknownParameter.get_by_id(unknown_parameter.id))

    # Setup two environments
    full_env_1 = await create_full_environment("proj1", "env1")
    full_env_2 = await create_full_environment("proj2", "env2")

    # Assert exists
    await assert_project_exists(*full_env_1, exists=True)
    await assert_project_exists(*full_env_2, exists=True)

    # Cascade delete project 1
    project_env1 = full_env_1[0]
    await project_env1.delete_cascade()

    # Assert outcome
    await assert_project_exists(*full_env_1, exists=False)
    await assert_project_exists(*full_env_2, exists=True)


@pytest.mark.asyncio
async def test_environment(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()
    assert env.project == project.id

    await project.delete_cascade()

    projects = await data.Project.get_list()
    envs = await data.Environment.get_list()
    assert len(projects) == 0
    assert len(envs) == 0


@pytest.mark.asyncio
async def test_environment_no_environment_name(data_module):
    project = data.Project(name="test")
    await project.insert()
    with pytest.raises(AttributeError):
        data.Environment(project=project.id, repo_url="", repo_branch="")


@pytest.mark.asyncio
async def test_environment_no_project_id(data_module):
    project = data.Project(name="test")
    await project.insert()
    with pytest.raises(AttributeError):
        data.Environment(name="dev", repo_url="", repo_branch="")


@pytest.mark.asyncio
async def test_environment_cascade_content_only(data_module):
    project = data.Project(name="proj")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    agent_proc = data.AgentProcess(hostname="testhost",
                                   environment=env.id,
                                   first_seen=datetime.datetime.now(),
                                   last_seen=datetime.datetime.now(),
                                   sid=uuid.uuid4())
    await agent_proc.insert()

    agi1 = data.AgentInstance(process=agent_proc.id, name="agi1", tid=env.id)
    await agi1.insert()
    agi2 = data.AgentInstance(process=agent_proc.id, name="agi2", tid=env.id)
    await agi2.insert()

    agent = data.Agent(environment=env.id, name="agi1", last_failover=datetime.datetime.now(), paused=False,
                       primary=agi1.id)
    await agent.insert()

    version = int(time.time())
    cm = data.ConfigurationModel(version=version, environment=env.id)
    await cm.insert()

    resource_ids = []
    for i in range(5):
        path = "/etc/file" + str(i)
        key = "std::File[agent1,path=" + path + "]"
        res1 = data.Resource.new(environment=env.id, resource_version_id=key + ",v=%d" % version,
                                 attributes={"path": path})
        await res1.insert()
        resource_ids.append(res1.id)

    code = data.Code(version=version, resource="std::File", environment=env.id)
    await code.insert()

    unknown_parameter = data.UnknownParameter(name="test", environment=env.id, version=version, source="")
    await unknown_parameter.insert()

    await env.set(data.AUTO_DEPLOY, True)

    assert (await data.Project.get_by_id(project.id)) is not None
    assert (await data.Environment.get_by_id(env.id)) is not None
    assert (await data.AgentProcess.get_by_id(agent_proc.id)) is not None
    assert (await data.AgentInstance.get_by_id(agi1.id)) is not None
    assert (await data.AgentInstance.get_by_id(agi2.id)) is not None
    assert (await data.Agent.get_by_id(agent.id)) is not None
    for current_id in resource_ids:
        assert (await data.Resource.get_by_id(current_id)) is not None
    assert (await data.Code.get_by_id(code.id)) is not None
    assert (await data.UnknownParameter.get_by_id(unknown_parameter.id)) is not None
    assert (await env.get(data.AUTO_DEPLOY)) is True

    await env.delete_cascade(only_content=True)

    assert (await data.Project.get_by_id(project.id)) is not None
    assert (await data.Environment.get_by_id(env.id)) is not None
    assert (await data.AgentProcess.get_by_id(agent_proc.id)) is None
    assert (await data.AgentInstance.get_by_id(agi1.id)) is None
    assert (await data.AgentInstance.get_by_id(agi2.id)) is None
    assert (await data.Agent.get_by_id(agent.id)) is None
    for current_id in resource_ids:
        assert (await data.Resource.get_by_id(current_id)) is None
    assert (await data.Code.get_by_id(code.id)) is None
    assert (await data.UnknownParameter.get_by_id(unknown_parameter.id)) is None
    assert (await env.get(data.AUTO_DEPLOY)) is True


@pytest.mark.asyncio
async def test_environment_set_setting_parameter(data_module):
    project = data.Project(name="proj")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    assert (await env.get(data.AUTO_DEPLOY)) is False
    await env.set(data.AUTO_DEPLOY, True)
    assert (await env.get(data.AUTO_DEPLOY)) is True
    await env.unset(data.AUTO_DEPLOY)
    assert (await env.get(data.AUTO_DEPLOY)) is False

    with pytest.raises(KeyError):
        await env.set("set_non_existing_parameter", 1)
    with pytest.raises(KeyError):
        await env.get("get_non_existing_parameter")
    with pytest.raises(AttributeError):
        await env.set(data.AUTO_DEPLOY, 5)


@pytest.mark.asyncio
async def test_environment_deprecated_setting(data_module, caplog):
    project = data.Project(name="proj")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    for (deprecated_option, new_option) in [(data.AUTOSTART_AGENT_INTERVAL, data.AUTOSTART_AGENT_DEPLOY_INTERVAL),
                                            (data.AUTOSTART_SPLAY, data.AUTOSTART_AGENT_DEPLOY_SPLAY_TIME)]:
        await env.set(deprecated_option, 22)
        caplog.clear()
        assert (await env.get(new_option)) == 22
        assert "Config option %s is deprecated. Use %s instead." % (deprecated_option, new_option) in caplog.text

        await env.set(new_option, 23)
        caplog.clear()
        assert (await env.get(new_option)) == 23
        assert "Config option %s is deprecated. Use %s instead." % (deprecated_option, new_option) not in caplog.text

        await env.unset(deprecated_option)
        caplog.clear()
        assert (await env.get(new_option)) == 23
        assert "Config option %s is deprecated. Use %s instead." % (deprecated_option, new_option) not in caplog.text


@pytest.mark.asyncio
async def test_agent_process(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    sid = uuid.uuid4()
    agent_proc = data.AgentProcess(hostname="testhost",
                                   environment=env.id,
                                   first_seen=datetime.datetime.now(),
                                   last_seen=datetime.datetime.now(),
                                   sid=sid)
    await agent_proc.insert()

    agi1 = data.AgentInstance(process=agent_proc.id, name="agi1", tid=env.id)
    await agi1.insert()
    agi2 = data.AgentInstance(process=agent_proc.id, name="agi2", tid=env.id)
    await agi2.insert()

    agent_procs = await data.AgentProcess.get_by_env(env=env.id)
    assert len(agent_procs) == 1
    assert agent_procs[0].id == agent_proc.id

    assert (await data.AgentProcess.get_by_sid(sid)).id == agent_proc.id
    assert (await data.AgentProcess.get_by_sid(uuid.UUID(int=1))) is None

    live_procs = await data.AgentProcess.get_live()
    assert len(live_procs) == 1
    assert live_procs[0].id == agent_proc.id

    live_by_env_procs = await data.AgentProcess.get_live_by_env(env=env.id)
    assert len(live_by_env_procs) == 1
    assert live_by_env_procs[0].id == agent_proc.id

    await agent_proc.update_fields(expired=datetime.datetime.now())

    live_procs = await data.AgentProcess.get_live()
    assert len(live_procs) == 0

    live_by_env_procs = await data.AgentProcess.get_live_by_env(env=env.id)
    assert len(live_by_env_procs) == 0

    await agent_proc.delete_cascade()

    assert (await data.AgentProcess.get_by_id(agent_proc.id)) is None
    assert (await data.AgentInstance.get_by_id(agi1.id)) is None
    assert (await data.AgentInstance.get_by_id(agi2.id)) is None


@pytest.mark.asyncio
async def test_agent_instance(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    sid = uuid.uuid4()
    agent_proc = data.AgentProcess(hostname="testhost",
                                   environment=env.id,
                                   first_seen=datetime.datetime.now(),
                                   last_seen=datetime.datetime.now(),
                                   sid=sid)
    await agent_proc.insert()

    agi1_name = "agi1"
    agi1 = data.AgentInstance(process=agent_proc.id, name=agi1_name, tid=env.id)
    await agi1.insert()
    agi2_name = "agi2"
    agi2 = data.AgentInstance(process=agent_proc.id, name=agi2_name, tid=env.id)
    await agi2.insert()

    active_instances = await data.AgentInstance.active()
    assert len(active_instances) == 2
    assert agi1.id in [x.id for x in active_instances]
    assert agi2.id in [x.id for x in active_instances]

    current_instances = await data.AgentInstance.active_for(env.id, agi1_name)
    assert len(current_instances) == 1
    assert current_instances[0].id == agi1.id
    current_instances = await data.AgentInstance.active_for(env.id, agi2_name)
    assert len(current_instances) == 1
    assert current_instances[0].id == agi2.id

    await agi1.update_fields(expired=datetime.datetime.now())

    active_instances = await data.AgentInstance.active()
    assert len(active_instances) == 1
    assert agi1.id not in [x.id for x in active_instances]
    assert agi2.id in [x.id for x in active_instances]

    current_instances = await data.AgentInstance.active_for(env.id, agi1_name)
    assert len(current_instances) == 0
    current_instances = await data.AgentInstance.active_for(env.id, agi2_name)
    assert len(current_instances) == 1
    assert current_instances[0].id == agi2.id


@pytest.mark.asyncio
async def test_agent(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    sid = uuid.uuid4()
    agent_proc = data.AgentProcess(hostname="testhost",
                                   environment=env.id,
                                   first_seen=datetime.datetime.now(),
                                   last_seen=datetime.datetime.now(),
                                   sid=sid)
    await agent_proc.insert()

    agi1_name = "agi1"
    agi1 = data.AgentInstance(process=agent_proc.id, name=agi1_name, tid=env.id)
    await agi1.insert()

    agent1 = data.Agent(environment=env.id, name="agi1_agent1", last_failover=datetime.datetime.now(), paused=False,
                        primary=agi1.id)
    await agent1.insert()
    agent2 = data.Agent(environment=env.id, name="agi1_agent2", paused=False)
    await agent2.insert()
    agent3 = data.Agent(environment=env.id, name="agi1_agent3", paused=True)
    await agent3.insert()

    agents = await data.Agent.get_list()
    assert len(agents) == 3
    for agent in agents:
        assert agent.id in [agent1.id, agent2.id, agent3.id]

    for agent in [agent1, agent2, agent3]:
        retrieved_agent = await agent.get(agent.environment, agent.name)
        assert retrieved_agent is not None
        assert retrieved_agent.id == agent.id

    assert agent1.get_status() == "up"
    assert agent2.get_status() == "down"
    assert agent3.get_status() == "paused"

    for agent in [agent1, agent2, agent3]:
        assert agent.to_dict()["state"] == agent.get_status()

    await agent1.update_fields(paused=True)
    assert agent1.get_status() == "paused"

    await agent2.update_fields(primary=agi1.id)
    assert agent2.get_status() == "up"

    await agent3.update_fields(paused=False)
    assert agent3.get_status() == "down"

    primary_instance = await data.AgentInstance.get_by_id(agent1.primary)
    primary_process = await data.AgentProcess.get_by_id(primary_instance.process)
    assert primary_process.id == agent_proc.id


@pytest.mark.asyncio
async def test_config_model(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    version = int(time.time())
    cm = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(),
                                 total=1, version_info={})
    await cm.insert()

    # create resources
    key = "std::File[agent1,path=/etc/motd]"
    res1 = data.Resource.new(environment=env.id, resource_version_id=key + ",v=%d" % version, attributes={"path": "/etc/motd"})
    await res1.insert()

    agents = await data.ConfigurationModel.get_agents(env.id, version)
    assert len(agents) == 1
    assert "agent1" in agents


@pytest.mark.asyncio
async def test_model_list(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    for version in range(1, 20):
        cm = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=0,
                                     version_info={})
        await cm.insert()

    versions = await data.ConfigurationModel.get_versions(env.id, 0, 1)
    assert len(versions) == 1
    assert versions[0].version == 19

    versions = await data.ConfigurationModel.get_versions(env.id, 1, 1)
    assert len(versions) == 1
    assert versions[0].version == 18

    versions = await data.ConfigurationModel.get_versions(env.id)
    assert len(versions) == 19
    assert versions[0].version == 19
    assert versions[-1].version == 1

    versions = await data.ConfigurationModel.get_versions(env.id, 10)
    assert len(versions) == 9
    assert versions[0].version == 9
    assert versions[-1].version == 1


@pytest.mark.asyncio
async def test_model_get_latest_version(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    cms = []
    for version in range(1, 5):
        cm = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=0,
                                     version_info={})
        await cm.insert()
        cms.append(cm)

    latest_version = await data.ConfigurationModel.get_latest_version(env.id)
    assert latest_version is None

    await cms[1].update_fields(released=True)
    latest_version = await data.ConfigurationModel.get_latest_version(env.id)
    assert latest_version.version == 2

    await cms[3].update_fields(released=True)
    latest_version = await data.ConfigurationModel.get_latest_version(env.id)
    assert latest_version.version == 4


@pytest.mark.asyncio
async def test_model_set_ready(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    version = int(time.time())
    cm = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=0,
                                 version_info={})
    await cm.insert()

    amount_of_done_resources = cm.done
    assert amount_of_done_resources == 0

    path = "/etc/file"
    key = "std::File[agent1,path=" + path + "]"
    resource = data.Resource.new(environment=env.id, resource_version_id=key + ",v=%d" % version,
                                 attributes={"path": path})
    await resource.insert()

    assert cm.done == 0

    await data.ConfigurationModel.set_ready(env.id, version, resource.id, resource.resource_id,
                                            const.ResourceState.deployed)

    cm = await data.ConfigurationModel.get_by_id(cm.id)
    assert cm.done == 1


@pytest.mark.asyncio
async def test_model_delete_cascade(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    version = int(time.time())
    cm = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=0,
                                 version_info={})
    await cm.insert()

    path = "/etc/file"
    key = "std::File[agent1,path=" + path + "]"
    resource = data.Resource.new(environment=env.id, resource_version_id=key + ",v=%d" % version,
                                 attributes={"path": path})
    await resource.insert()

    code = data.Code(version=version, resource="std::File", environment=env.id)
    await code.insert()

    unknown_parameter = data.UnknownParameter(name="test", environment=env.id, version=version, source="")
    await unknown_parameter.insert()

    await cm.delete_cascade()

    assert (await data.ConfigurationModel.get_by_id(cm.id)) is None
    assert (await data.Resource.get_by_id(resource.id)) is None
    assert (await data.Code.get_by_id(code.id)) is None
    assert (await data.UnknownParameter.get_by_id(unknown_parameter.id)) is None


@pytest.mark.asyncio
async def test_undeployable_cache_lazy(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    version = 1
    cm1 = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=5, version_info={},
                                  released=False, deployed=False)
    await cm1.insert()
    await populate_model(env.id, version)

    assert cm1.undeployable is None

    undep = await cm1.get_undeployable()
    assert undep == ["std::File[agent1,path=/tmp/%d]" % 3]

    assert cm1.undeployable is not None

    undep = await cm1.get_undeployable()
    assert undep == ["std::File[agent1,path=/tmp/%d]" % 3]

    cm1 = await data.ConfigurationModel.get_version(env.id, version)

    assert cm1.undeployable is not None

    undep = await cm1.get_undeployable()
    assert undep == ["std::File[agent1,path=/tmp/%d]" % 3]


@pytest.mark.asyncio
async def test_undeployable_skip_cache_lazy(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    version = 2
    cm1 = data.ConfigurationModel(environment=env.id,
                                  version=version,
                                  date=datetime.datetime.now(),
                                  total=5,
                                  version_info={},
                                  released=False, deployed=False)
    await cm1.insert()
    await populate_model(env.id, version)

    assert cm1.skipped_for_undeployable is None

    undep = await cm1.get_skipped_for_undeployable()
    assert undep == ["std::File[agent1,path=/tmp/%d]" % 4, "std::File[agent1,path=/tmp/%d]" % 5]

    assert cm1.skipped_for_undeployable is not None

    undep = await cm1.get_skipped_for_undeployable()
    assert undep == ["std::File[agent1,path=/tmp/%d]" % 4, "std::File[agent1,path=/tmp/%d]" % 5]

    cm1 = await data.ConfigurationModel.get_version(env.id, version)

    assert cm1.skipped_for_undeployable is not None

    undep = await cm1.get_skipped_for_undeployable()
    assert undep == ["std::File[agent1,path=/tmp/%d]" % 4, "std::File[agent1,path=/tmp/%d]" % 5]


async def populate_model(env_id, version):

    def get_path(n):
        return "/tmp/%d" % n

    def get_id(n):
        return "std::File[agent1,path=/tmp/%d],v=%s" % (n, version)

    def get_resource(n, depends, status=const.ResourceState.available):
        requires = [get_id(z) for z in depends]
        return data.Resource.new(environment=env_id, resource_version_id=get_id(n),
                                 status=status,
                                 attributes={"path": get_path(n),
                                             "purge_on_delete": False,
                                             "purged": False,
                                             "requires": requires})

    res1 = get_resource(1, [])
    await res1.insert()

    res2 = get_resource(2, [1])
    await res2.insert()

    res3 = get_resource(3, [], const.ResourceState.undefined)
    await res3.insert()

    res4 = get_resource(4, [3])
    await res4.insert()

    res5 = get_resource(5, [4])
    await res5.insert()


@pytest.mark.asyncio
async def test_resource_purge_on_delete(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    # model 1
    version = 1
    cm1 = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=2, version_info={},
                                  released=True, deployed=True)
    await cm1.insert()

    res11 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/etc/motd],v=%s" % version,
                              status=const.ResourceState.deployed,
                              attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
    await res11.insert()

    res12 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent2,path=/etc/motd],v=%s" % version,
                              status=const.ResourceState.deployed,
                              attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": True})
    await res12.insert()

    # model 2 (multiple undeployed versions)
    while version < 10:
        version += 1
        cm2 = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=1,
                                      version_info={}, released=False, deployed=False)
        await cm2.insert()

        res21 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent5,path=/etc/motd],v=%s" % version,
                                  status=const.ResourceState.available,
                                  attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
        await res21.insert()

    # model 3
    version += 1
    cm3 = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=0, version_info={})
    await cm3.insert()

    to_purge = await data.Resource.get_deleted_resources(env.id, version, set())

    assert len(to_purge) == 1
    assert to_purge[0].model == 1
    assert to_purge[0].resource_id == "std::File[agent1,path=/etc/motd]"


@pytest.mark.asyncio
async def test_issue_422(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    # model 1
    version = 1
    cm1 = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=1, version_info={},
                                  released=True, deployed=True)
    await cm1.insert()

    res11 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/etc/motd],v=%s" % version,
                              status=const.ResourceState.deployed,
                              attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
    await res11.insert()

    # model 2 (multiple undeployed versions)
    version += 1
    cm2 = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=1,
                                  version_info={}, released=False, deployed=False)
    await cm2.insert()

    res21 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/etc/motd],v=%s" % version,
                              status=const.ResourceState.available,
                              attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
    await res21.insert()

    # model 3
    version += 1
    cm3 = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=0, version_info={})
    await cm3.insert()

    to_purge = await data.Resource.get_deleted_resources(env.id, version, set())

    assert len(to_purge) == 1
    assert to_purge[0].model == 1
    assert to_purge[0].resource_id == "std::File[agent1,path=/etc/motd]"


@pytest.mark.asyncio
async def test_get_latest_resource(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    key = "std::File[agent1,path=/etc/motd]"
    assert (await data.Resource.get_latest_version(env.id, key)) is None

    res11 = data.Resource.new(environment=env.id, resource_version_id=key + ",v=1", status=const.ResourceState.deployed,
                              attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
    await res11.insert()

    res12 = data.Resource.new(environment=env.id, resource_version_id=key + ",v=2", status=const.ResourceState.deployed,
                              attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": True})
    await res12.insert()

    res = await data.Resource.get_latest_version(env.id, key)
    assert res.model == 2


@pytest.mark.asyncio
async def test_get_resources(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    resource_ids = []
    for i in range(1, 11):
        res = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/tmp/file%d],v=1" % i,
                                status=const.ResourceState.deployed,
                                attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
        await res.insert()
        resource_ids.append(res.resource_version_id)

    resources = await data.Resource.get_resources(env.id, resource_ids)
    assert len(resources) == len(resource_ids)
    assert sorted([x.resource_version_id for x in resources]) == sorted(resource_ids)

    resources = await data.Resource.get_resources(env.id, [resource_ids[0], "abcd"])
    assert len(resources) == 1

    resources = await data.Resource.get_resources(env.id, [])
    assert len(resources) == 0


@pytest.mark.asyncio
async def test_get_resources_for_version(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    resource_ids_version_one = []
    for i in range(1, 11):
        res = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/tmp/file%d],v=1" % i,
                                status=const.ResourceState.deployed,
                                attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
        await res.insert()
        resource_ids_version_one.append(res.resource_version_id)

    resource_ids_version_two = []
    for i in range(11, 21):
        res = data.Resource.new(environment=env.id, resource_version_id="std::File[agent2,path=/tmp/file%d],v=2" % i,
                                status=const.ResourceState.deployed,
                                attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
        await res.insert()
        resource_ids_version_two.append(res.resource_version_id)

    async def make_with_status(i, status):
        res = data.Resource.new(environment=env.id, resource_version_id="std::File[agent3,path=/tmp/file%d],v=3" % i,
                                status=status,
                                attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
        await res.insert()
        return res.resource_version_id

    d = await make_with_status(1, const.ResourceState.deployed)
    s = await make_with_status(2, const.ResourceState.skipped)
    su = await make_with_status(3, const.ResourceState.skipped_for_undefined)
    u = await make_with_status(4, const.ResourceState.undefined)

    resources = await data.Resource.get_resources_for_version(env.id, 1)
    assert len(resources) == 10
    assert sorted(resource_ids_version_one) == sorted([x.resource_version_id for x in resources])
    resources = await data.Resource.get_resources_for_version(env.id, 2)
    assert len(resources) == 10
    assert sorted(resource_ids_version_two) == sorted([x.resource_version_id for x in resources])
    resources = await data.Resource.get_resources_for_version(env.id, 4)
    assert resources == []

    resources = await data.Resource.get_resources_for_version(env.id, 3)
    assert len(resources) == 4
    assert sorted([x.resource_version_id for x in resources]) == sorted([d, s, u, su])

    resources = await data.Resource.get_resources_for_version(env.id, 3, include_undefined=False)
    assert len(resources) == 2
    assert sorted([x.resource_version_id for x in resources]) == sorted([d, s])


@pytest.mark.asyncio
async def test_escaped_resources(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    routes = {"8.0.0.0/8": "1.2.3.4", "0.0.0.0/0": "127.0.0.1"}
    res = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,name=router],v=1",
                            status=const.ResourceState.deployed,
                            attributes={"name": "router", "purge_on_delete": True, "purged": False, "routes": routes})
    await res.insert()
    resource_id = res.resource_version_id

    resources = await data.Resource.get_resources(env.id, [resource_id])
    assert len(resources) == 1

    assert resources[0].attributes["routes"] == routes


@pytest.mark.asyncio
async def test_resource_provides(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    res1 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/etc/file1],v=1",
                             status=const.ResourceState.deployed,
                             attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
    res2 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/etc/file2],v=1",
                             status=const.ResourceState.deployed,
                             attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
    res1.provides.append(res2.resource_version_id)

    assert len(res1.provides) == 1
    assert len(res2.provides) == 0
    assert res1.provides[0] == res2.resource_version_id
    assert res2.provides == []

    await res1.insert()
    await res2.insert()

    res1 = await data.Resource.get(env.id, res1.resource_version_id)
    res2 = await data.Resource.get(env.id, res2.resource_version_id)

    assert len(res1.provides) == 1
    assert len(res2.provides) == 0
    assert res1.provides[0] == res2.resource_version_id
    assert res2.provides == []


@pytest.mark.asyncio
async def test_resource_hash(data_module):
    env_id = uuid.uuid4()
    res1 = data.Resource.new(environment=env_id, resource_version_id="std::File[agent1,path=/etc/file1],v=1",
                             status=const.ResourceState.deployed,
                             attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
    res2 = data.Resource.new(environment=env_id, resource_version_id="std::File[agent1,path=/etc/file1],v=2",
                             status=const.ResourceState.deployed,
                             attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": False})
    res3 = data.Resource.new(environment=env_id, resource_version_id="std::File[agent1,path=/etc/file1],v=3",
                             status=const.ResourceState.deployed,
                             attributes={"path": "/etc/motd", "purge_on_delete": True, "purged": True})
    await res1.insert()
    await res2.insert()
    await res3.insert()

    assert res1.attribute_hash is not None
    assert res1.attribute_hash == res2.attribute_hash
    assert res3.attribute_hash is not None
    assert res1.attribute_hash != res3.attribute_hash

    readres = await data.Resource.get_resources(env_id,
                                                [res1.resource_version_id, res2.resource_version_id, res3.resource_version_id])
    res1, res2, res3 = readres

    assert res1.attribute_hash is not None
    assert res1.attribute_hash == res2.attribute_hash
    assert res3.attribute_hash is not None
    assert res1.attribute_hash != res3.attribute_hash


@pytest.mark.asyncio
async def test_resources_report(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    # model 1
    version = 1
    cm1 = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=1,
                                  version_info={},
                                  released=True, deployed=True)
    await cm1.insert()

    res11 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/etc/file1],v=%s" % version,
                              status=const.ResourceState.deployed, last_deploy=datetime.datetime(2018, 7, 14, 12, 30),
                              attributes={"path": "/etc/file1"})
    await res11.insert()
    res12 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/etc/file2],v=%s" % version,
                              status=const.ResourceState.deployed, last_deploy=datetime.datetime(2018, 7, 14, 12, 30),
                              attributes={"path": "/etc/file2"})
    await res12.insert()

    # model 2
    version += 1
    cm2 = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=1,
                                  version_info={}, released=False, deployed=False)
    await cm2.insert()
    res21 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/etc/file1],v=%s" % version,
                              status=const.ResourceState.available,
                              attributes={"path": "/etc/file1"})
    await res21.insert()

    # model 3
    version += 1
    cm3 = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=1,
                                  version_info={}, released=True, deployed=True)
    await cm3.insert()

    res31 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/etc/file2],v=%s" % version,
                              status=const.ResourceState.deployed, last_deploy=datetime.datetime(2018, 7, 14, 14, 30),
                              attributes={"path": "/etc/file2"})
    await res31.insert()

    report = await data.Resource.get_resources_report(env.id)
    assert len(report) == 2
    report_as_map = {x["resource_id"]: x for x in report}
    assert "std::File[agent1,path=/etc/file1]" in report_as_map
    assert "std::File[agent1,path=/etc/file2]" in report_as_map

    assert report_as_map["std::File[agent1,path=/etc/file1]"]["resource_type"] == "std::File"
    assert report_as_map["std::File[agent1,path=/etc/file1]"]["deployed_version"] == 1
    assert report_as_map["std::File[agent1,path=/etc/file1]"]["latest_version"] == 2
    assert report_as_map["std::File[agent1,path=/etc/file1]"]["last_deploy"] == datetime.datetime(2018, 7, 14, 12, 30)
    assert report_as_map["std::File[agent1,path=/etc/file1]"]["agent"] == "agent1"

    assert report_as_map["std::File[agent1,path=/etc/file1]"]["resource_type"] == "std::File"
    assert report_as_map["std::File[agent1,path=/etc/file2]"]["deployed_version"] == 3
    assert report_as_map["std::File[agent1,path=/etc/file2]"]["latest_version"] == 3
    assert report_as_map["std::File[agent1,path=/etc/file2]"]["last_deploy"] == datetime.datetime(2018, 7, 14, 14, 30)
    assert report_as_map["std::File[agent1,path=/etc/file2]"]["agent"] == "agent1"


@pytest.mark.asyncio
async def test_resource_get_requires(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    res1 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/etc/file1],v=1",
                             status=const.ResourceState.deployed,
                             attributes={"path": "/etc/motd"})
    await res1.insert()
    res2 = data.Resource.new(environment=env.id, resource_version_id="std::File[agent1,path=/etc/file2],v=1",
                             status=const.ResourceState.deployed,
                             attributes={"path": "/etc/motd", "requires": [res1.resource_version_id]})
    await res2.insert()

    assert (await data.Resource.get_requires(env.id, 1, res2.resource_version_id)) == []
    resources = await data.Resource.get_requires(env.id, 1, res1.resource_version_id)
    assert len(resources) == 1
    assert resources[0].id == res2.id


@pytest.mark.asyncio
async def test_resources_delete_cascade(data_module):
    project = data.Project(name="test")
    await project.insert()
    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    version = 1
    cm = data.ConfigurationModel(environment=env.id, version=version, date=datetime.datetime.now(), total=1,
                                 version_info={}, released=True, deployed=True)
    await cm.insert()

    res1 = data.Resource.new(environment=env.id,
                             resource_version_id="std::File[agent1,path=/etc/file1],v=%s" % version,
                             status=const.ResourceState.deployed, last_deploy=datetime.datetime.now(),
                             attributes={"path": "/etc/file1"})
    await res1.insert()
    res2 = data.Resource.new(environment=env.id,
                             resource_version_id="std::File[agent1,path=/etc/file2],v=%s" % version,
                             status=const.ResourceState.deployed, last_deploy=datetime.datetime.now(),
                             attributes={"path": "/etc/file1"})
    await res2.insert()
    resource_action1 = data.ResourceAction(environment=env.id, resource_version_ids=[res1.resource_version_id],
                                           action_id=uuid.uuid4(), action=const.ResourceAction.deploy,
                                           started=datetime.datetime.now())
    await resource_action1.insert()
    resource_action2 = data.ResourceAction(environment=env.id, resource_version_ids=[res2.resource_version_id],
                                           action_id=uuid.uuid4(), action=const.ResourceAction.deploy,
                                           started=datetime.datetime.now())
    await resource_action2.insert()

    await res1.delete_cascade()

    assert (await data.Resource.get_by_id(res1.id)) is None
    assert (await data.ResourceAction.get_by_id(resource_action1.id)) is None
    resource = await data.Resource.get_by_id(res2.id)
    assert resource is not None
    assert resource.id == res2.id
    resource_action = await data.ResourceAction.get_by_id(resource_action2.id)
    assert resource_action is not None
    assert resource_action.id == resource_action2.id


@pytest.mark.asyncio
async def test_resource_action(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    action_id = uuid.uuid4()
    resource_action = data.ResourceAction(environment=env.id, resource_version_ids=[], action_id=action_id,
                                          action=const.ResourceAction.deploy, started=datetime.datetime.now())
    await resource_action.insert()

    resource_action.add_changes({"rid": {"field1": {"old": "a", "new": "b"}, "field2": {}}})
    await resource_action.save()

    resource_action.add_changes({"rid": {"field2": {"old": "c", "new": "d"}, "field3": {}}})
    await resource_action.save()

    resource_action.add_logs([{}, {}])
    await resource_action.save()

    resource_action.add_logs([{}, {}])
    await resource_action.save()

    ra = await data.ResourceAction.get_by_id(resource_action.id)
    assert len(ra.changes["rid"]) == 3
    assert len(ra.messages) == 4

    assert ra.changes["rid"]["field1"]["old"] == "a"
    assert ra.changes["rid"]["field1"]["new"] == "b"
    assert ra.changes["rid"]["field2"]["old"] == "c"
    assert ra.changes["rid"]["field2"]["new"] == "d"
    assert ra.changes["rid"]["field3"] == {}


@pytest.mark.asyncio
async def test_resource_action_get_logs(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    for i in range(1, 11):
        action_id = uuid.uuid4()
        resource_action = data.ResourceAction(environment=env.id, resource_version_ids=[uuid.UUID(int=1)], action_id=action_id,
                                              action=const.ResourceAction.deploy, started=datetime.datetime.now())
        await resource_action.insert()
        resource_action.add_logs(["test" + str(i)])
        await resource_action.save()

    action_id = uuid.uuid4()
    resource_action = data.ResourceAction(environment=env.id, resource_version_ids=[uuid.UUID(int=1)], action_id=action_id,
                                          action=const.ResourceAction.dryrun, started=datetime.datetime.now())
    await resource_action.insert()
    resource_action.add_logs(["log", "message"])
    await resource_action.save()

    resource_actions = await data.ResourceAction.get_log(env.id, uuid.UUID(int=1))
    assert len(resource_actions) == 11
    for i in range(len(resource_actions)):
        action = resource_actions[i]
        if i == 0:
            assert action.action == const.ResourceAction.dryrun
        else:
            assert action.action == const.ResourceAction.deploy
    resource_actions = await data.ResourceAction.get_log(env.id, uuid.UUID(int=1), const.ResourceAction.dryrun.name)
    assert len(resource_actions) == 1
    assert resource_actions[0].action == const.ResourceAction.dryrun
    assert sorted(resource_actions[0].messages) == sorted(["log", "message"])
    resource_actions = await data.ResourceAction.get_log(env.id, uuid.UUID(int=1), const.ResourceAction.deploy.name, limit=2)
    assert len(resource_actions) == 2
    assert resource_actions[0].messages == ["test10"]
    assert resource_actions[1].messages == ["test9"]


@pytest.mark.asyncio
async def test_data_document_recursion(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    now = datetime.datetime.now()
    ra = data.ResourceAction(environment=env.id, resource_version_ids=["id"], action_id=uuid.uuid4(),
                             action=const.ResourceAction.store, started=now, finished=now,
                             messages=[data.LogLine.log(logging.INFO, "Successfully stored version %(version)d",
                                                        version=2)])
    await ra.insert()


@pytest.mark.asyncio
async def test_purgelog_test(data_module):
    project = data.Project(name="test")
    await project.insert()

    env = data.Environment(name="dev", project=project.id, repo_url="", repo_branch="")
    await env.insert()

    # ResourceAction 1
    timestamp_ra1 = datetime.datetime.now() - datetime.timedelta(days=8)
    log_line_ra1 = data.LogLine.log(logging.INFO, "Successfully stored version %(version)d", version=1)
    ra1 = data.ResourceAction(environment=env.id, resource_version_ids=["id"], action_id=uuid.uuid4(),
                              action=const.ResourceAction.store, started=timestamp_ra1, finished=datetime.datetime.now(),
                              messages=[log_line_ra1])
    await ra1.insert()

    # ResourceAction 2
    timestamp_ra2 = datetime.datetime.now() - datetime.timedelta(days=6)
    log_line_ra2 = data.LogLine.log(logging.INFO, "Successfully stored version %(version)d", version=2)
    ra2 = data.ResourceAction(environment=env.id, resource_version_ids=["id"], action_id=uuid.uuid4(),
                              action=const.ResourceAction.store, started=timestamp_ra2, finished=datetime.datetime.now(),
                              messages=[log_line_ra2])
    await ra2.insert()

    assert len(await data.ResourceAction.get_list()) == 2
    await data.ResourceAction.purge_logs()
    assert len(await data.ResourceAction.get_list()) == 1
    remaining_resource_action = (await data.ResourceAction.get_list())[0]
    assert remaining_resource_action.id == ra2.id
