Beispiel #1
0
def test_create_opc_client(
    event_loop: asyncio.AbstractEventLoop,
    expect_user_pass: bool,
    mocker: MockerFixture,
    url: str,
    with_cert_file: bool,
) -> None:
    mocked_asyncua_client = mocker.patch("asyncua.Client")
    mocked_asyncua_client.return_value.set_security = mocker.AsyncMock()
    config = mocker.Mock(server_url=url)
    if with_cert_file:
        config.configure_mock(cert_file="certFile", private_key_file="keyFile")
    else:
        config.configure_mock(cert_file=None, private_key_file=None)
    opcua_client = OPCUAClient(config, mocker.Mock(), mocker.Mock(),
                               mocker.Mock())
    created_client = event_loop.run_until_complete(
        opcua_client._create_opc_client())
    assert mocked_asyncua_client.call_args_list == [
        mocker.call(url="//opc/server.url")
    ]
    set_user = cast(Mock, created_client.set_user)
    set_password = cast(Mock, created_client.set_password)
    expected_set_user_call = []
    expected_set_pw_call = []
    expected_set_security_call = []
    if expect_user_pass:
        expected_set_user_call.append(mocker.call("user"))
        expected_set_pw_call.append(mocker.call("pass"))
    if with_cert_file:
        expected_set_security_call.append(
            mocker.call(SecurityPolicyBasic256Sha256, "certFile", "keyFile"))
    assert set_user.call_args_list == expected_set_user_call
    assert set_password.call_args_list == expected_set_pw_call
    assert created_client.set_security.await_args_list == expected_set_security_call
Beispiel #2
0
async def test_run(
    mocker: MockerFixture,
    root: Path,
) -> None:
    """
    Test ``run``.
    """
    builder = mocker.MagicMock()
    builder.process_post = mocker.AsyncMock()
    builder.process_site = mocker.AsyncMock()

    mocker.patch("nefelibata.cli.init.get_config")
    mocker.patch("nefelibata.cli.init.get_builders",
                 return_value={"builder": builder})

    _logger = mocker.patch("nefelibata.cli.init._logger")

    await init.run(root)

    builder.setup.assert_called()
    _logger.info.assert_called_with("Blog created!")

    assert (root / CONFIG_FILENAME).exists()
    assert (root / "posts/first/index.mkd").exists()

    # error if running again
    with pytest.raises(IOError) as excinfo:
        await init.run(root)
    assert str(
        excinfo.value) == "File /path/to/blog/nefelibata.yaml already exists!"
Beispiel #3
0
async def test_preinit(mocker: MockerFixture) -> None:
    from gravel.controllers.deployment.mgr import (
        DeploymentError,
        DeploymentMgr,
        InitStateEnum,
    )
    from gravel.controllers.nodes.systemdisk import OverlayError

    mgr: DeploymentMgr = DeploymentMgr()
    mocker.patch(
        "gravel.controllers.nodes.systemdisk.SystemDisk.exists",
        new=mocker.AsyncMock(return_value=False),
    )
    await mgr.preinit()
    assert mgr._preinited == True
    assert mgr._init_state == InitStateEnum.NONE

    mgr = DeploymentMgr()
    mocker.patch(
        "gravel.controllers.nodes.systemdisk.SystemDisk.exists",
        new=mocker.AsyncMock(return_value=True),
    )
    mocker.patch(
        "gravel.controllers.nodes.systemdisk.SystemDisk.enable",
        new=mocker.AsyncMock(side_effect=OverlayError),
    )

    raised = False
    try:
        await mgr.preinit()
    except DeploymentError:
        raised = True
    except Exception:
        assert False
    assert raised
    assert not mgr._preinited
    assert mgr._init_state == InitStateEnum.NONE

    mgr = DeploymentMgr()
    mocker.patch(
        "gravel.controllers.nodes.systemdisk.SystemDisk.enable",
        new=mocker.AsyncMock(),
    )

    await mgr.preinit()
    assert mgr._preinited
    assert mgr._init_state == InitStateEnum.INSTALLED
Beispiel #4
0
async def test_create(
    gstate: GlobalState,
    fs: fake_filesystem.FakeFilesystem,
    mocker: MockerFixture,
    get_data_contents: Callable[[str, str], str],
) -> None:
    async def mock_call(
        cmd: List[str],
    ) -> Tuple[int, Optional[str], Optional[str]]:
        return 0, None, None

    async def mock_lvm(args: str) -> None:
        if "lvcreate" in args:
            if "systemdisk" in args:
                fs.create_file("/dev/mapper/aquarium-systemdisk")
            elif "containers" in args:
                fs.create_file("/dev/mapper/aquarium-containers")

    from gravel.controllers.inventory.inventory import Inventory
    from gravel.controllers.inventory.nodeinfo import NodeInfoModel
    from gravel.controllers.nodes.systemdisk import (
        SystemDisk,
        UnavailableDeviceError,
        UnknownDeviceError,
    )

    nodeinfo: NodeInfoModel = NodeInfoModel.parse_raw(
        get_data_contents(DATA_DIR, "nodeinfo_real.json")
    )

    inventory: Inventory = gstate.inventory
    inventory.probe = mocker.AsyncMock()
    inventory._latest = nodeinfo

    systemdisk = SystemDisk(gstate)
    systemdisk.lvm = mock_lvm
    mocker.patch(
        "gravel.controllers.nodes.systemdisk.aqr_run_cmd", new=mock_call
    )
    throws = False
    try:
        await systemdisk.create("/dev/foobar")
    except UnknownDeviceError:
        throws = True
        pass
    assert throws

    nodeinfo.disks[0].available = False
    throws = False
    try:
        await systemdisk.create("/dev/vda")
    except UnavailableDeviceError:
        throws = True
        pass
    assert throws

    nodeinfo.disks[0].available = True
    await systemdisk.create("/dev/vda")
async def test_run_typecheck_in_sandbox(mocker: MockerFixture) -> None:
    semaphore = asyncio.Semaphore(1)
    mock_sandbox = mocker.AsyncMock()
    await run_typecheck_in_sandbox(mock_sandbox,
                                   "import this",
                                   semaphore=semaphore,
                                   python_version="3.8")
    mock_sandbox.run_typecheck.assert_called_once_with("import this",
                                                       python_version="3.8")
Beispiel #6
0
def test_run(mocker: MockerFixture) -> None:
    """
    Test ``run``.
    """
    main = mocker.AsyncMock()
    mocker.patch("nefelibata.console.main", main)

    console.run()

    main.assert_called()
Beispiel #7
0
async def test_main_build(mocker: MockerFixture) -> None:
    """
    Test ``main`` with the "build" action.
    """
    build = mocker.patch("nefelibata.console.build")
    build.run = mocker.AsyncMock()

    mocker.patch(
        "nefelibata.console.docopt",
        return_value={
            "--loglevel": "debug",
            "init": False,
            "new": False,
            "build": True,
            "publish": False,
            "ROOT_DIR": "/path/to/blog",
            "--force": False,
        },
    )
    await console.main()
    build.run.assert_called_with(Path("/path/to/blog"), False)

    mocker.patch(
        "nefelibata.console.docopt",
        return_value={
            "--loglevel": "debug",
            "init": False,
            "new": False,
            "build": True,
            "publish": False,
            "ROOT_DIR": "/path/to/blog",
            "--force": True,
        },
    )
    await console.main()
    build.run.assert_called_with(Path("/path/to/blog"), True)

    mocker.patch(
        "nefelibata.console.docopt",
        return_value={
            "--loglevel": "debug",
            "init": False,
            "new": False,
            "build": True,
            "publish": False,
            "ROOT_DIR": None,
            "--force": True,
        },
    )
    mocker.patch(
        "nefelibata.console.find_directory",
        return_value=Path("/path/to/blog"),
    )
    await console.main()
    build.run.assert_called_with(Path("/path/to/blog"), True)
Beispiel #8
0
async def test_mgr_start(
    gstate: GlobalState,
    fs: fake_filesystem.FakeFilesystem,
    mocker: MockerFixture,
) -> None:

    from gravel.controllers.nodes.deployment import NodeStageEnum
    from gravel.controllers.nodes.mgr import NodeError, NodeStateModel

    nodemgr = NodeMgr(gstate)
    assert nodemgr._state
    assert nodemgr.deployment_state.can_start()

    orig = nodemgr.deployment_state.can_start
    nodemgr.deployment_state.can_start = mocker.MagicMock(return_value=False)
    throws = False
    try:
        await nodemgr.start()
    except NodeError as e:
        assert "unstartable" in e.message
        throws = True
    assert throws
    nodemgr.deployment_state.can_start = orig

    nodemgr._deployment._state._stage = NodeStageEnum.NONE
    nodemgr._node_prepare = mocker.AsyncMock()
    await nodemgr.start()
    nodemgr._node_prepare.assert_called_once()  # type: ignore

    nodemgr._deployment._state._stage = NodeStageEnum.READY
    nodemgr._state = NodeStateModel(
        uuid="bba35d93-d4a5-48b3-804b-99c406555c89",
        address="1.2.3.4",
        hostname="foobar",
    )

    nodemgr._start_ceph = mocker.AsyncMock()
    nodemgr._node_start = mocker.AsyncMock()

    await nodemgr.start()
    nodemgr._start_ceph.assert_called_once()  # type: ignore
    nodemgr._node_start.assert_called_once()  # type: ignore
Beispiel #9
0
def test_interrupt(mocker: MockerFixture) -> None:
    """
    Test that ``CTRL-C`` stops the CLI.
    """
    main = mocker.AsyncMock(side_effect=KeyboardInterrupt())
    mocker.patch("nefelibata.console.main", main)
    _logger = mocker.patch("nefelibata.console._logger")

    console.run()

    _logger.info.assert_called_with("Stopping Nefelibata")
Beispiel #10
0
async def test_bootstrap_finisher_cb(gstate: GlobalState,
                                     mocker: MockerFixture,
                                     nodemgr: NodeMgr) -> None:

    from gravel.controllers.nodes.mgr import NodeInitStage

    nodemgr._init_stage = NodeInitStage.NONE
    assert await expect_assertion(nodemgr._post_bootstrap_finisher(True, None))
    nodemgr._init_stage = NodeInitStage.PREPARE
    assert await expect_assertion(nodemgr._post_bootstrap_finisher(True, None))
    nodemgr._init_stage = NodeInitStage.STARTED
    assert await expect_assertion(nodemgr._post_bootstrap_finisher(True, None))

    nodemgr._init_stage = NodeInitStage.AVAILABLE
    nodemgr._save_state = mocker.AsyncMock()
    nodemgr._post_bootstrap_config = mocker.AsyncMock()

    await nodemgr._post_bootstrap_finisher(True, None)

    nodemgr._save_state.assert_called_once()  # type: ignore
    nodemgr._post_bootstrap_config.assert_called_once()  # type: ignore
Beispiel #11
0
async def test_finish_deployment_cb(gstate: GlobalState, mocker: MockerFixture,
                                    nodemgr: NodeMgr) -> None:

    from gravel.controllers.nodes.mgr import NodeInitStage

    nodemgr._init_stage = NodeInitStage.NONE
    assert await expect_assertion(nodemgr._finish_deployment(True, None))
    nodemgr._init_stage = NodeInitStage.PREPARE
    assert await expect_assertion(nodemgr._finish_deployment(True, None))
    nodemgr._init_stage = NodeInitStage.STARTED
    assert await expect_assertion(nodemgr._finish_deployment(True, None))

    nodemgr._init_stage = NodeInitStage.AVAILABLE
    nodemgr._deployment.finish_deployment = mocker.MagicMock()
    nodemgr._load = mocker.AsyncMock()
    nodemgr._node_start = mocker.AsyncMock()

    await nodemgr._finish_deployment(True, None)

    nodemgr._deployment.finish_deployment.assert_called_once()  # type: ignore
    nodemgr._load.assert_called_once()  # type: ignore
    nodemgr._node_start.assert_called_once()  # type: ignore
Beispiel #12
0
async def test_obtain_images(
    gstate: GlobalState, mocker: MockerFixture
) -> None:

    orig_cephadm_pull_img = gstate.cephadm.pull_images
    gstate.cephadm.pull_images = mocker.AsyncMock()

    nodemgr = NodeMgr(gstate)
    ret = await nodemgr._obtain_images()
    assert ret
    gstate.cephadm.pull_images.assert_called_once()  # type: ignore

    from gravel.cephadm.cephadm import CephadmError

    gstate.cephadm.pull_images = mocker.AsyncMock(
        side_effect=CephadmError("foobar")
    )
    ret = await nodemgr._obtain_images()
    assert not ret
    gstate.cephadm.pull_images.assert_called_once()  # type: ignore

    gstate.cephadm.pull_images = orig_cephadm_pull_img
async def test_announcer_collect(
    mocker: MockerFixture,
    root: Path,
    config: Config,
    post: Post,
) -> None:
    """
    Test collecting interactions on posts and sites.
    """
    gemini_builder = Builder(root, config, "gemini://example.com/", "gemini")
    html_builder = Builder(root, config, "https://example.com/", "www")

    Client = mocker.patch("nefelibata.announcers.geminispace.Client")
    Client.return_value.get = mocker.AsyncMock()
    Client.return_value.get.return_value.read.return_value = b"""
### 3 cross-capsule backlinks

=> gemini://example.com/reply.gmi Re: This is your first post
=> gemini://example.com/another-reply.gmi Re: This is your first post
    """

    announcer = GeminispaceAnnouncer(root, config, builders=[gemini_builder])

    # site collect is no-op
    interactions = await announcer.collect_site()
    assert interactions == {}

    interactions = await announcer.collect_post(post)
    assert interactions == {
        "backlink,gemini://example.com/reply.gmi": Interaction(
            id="backlink,gemini://example.com/reply.gmi",
            name="Re: This is your first post",
            url="gemini://example.com/reply.gmi",
            type="backlink",
            timestamp=None,
        ),
        "backlink,gemini://example.com/another-reply.gmi": Interaction(
            id="backlink,gemini://example.com/another-reply.gmi",
            name="Re: This is your first post",
            url="gemini://example.com/another-reply.gmi",
            type="backlink",
            timestamp=None,
        ),
    }

    announcer = GeminispaceAnnouncer(root, config, builders=[html_builder])
    with pytest.raises(Exception) as excinfo:
        await announcer.collect_post(post)
    assert (
        str(excinfo.value) == "Geminispace announcer only works with `gemini://` builds"
    )
Beispiel #14
0
async def test_node_start(
    gstate: GlobalState,
    mocker: MockerFixture,
    fs: fake_filesystem.FakeFilesystem,
    nodemgr: NodeMgr,
) -> None:

    from gravel.controllers.nodes.deployment import NodeStageEnum
    from gravel.controllers.nodes.mgr import NodeInitStage

    nodemgr._deployment._state._stage = NodeStageEnum.READY

    nodemgr._obtain_state = mocker.AsyncMock()
    nodemgr._load = mocker.AsyncMock()
    nodemgr._incoming_msg_task = mocker.AsyncMock()
    nodemgr._connmgr.start_receiving = mocker.MagicMock()

    await nodemgr._node_start()

    assert nodemgr._init_stage == NodeInitStage.STARTED
    nodemgr._obtain_state.assert_called_once()  # type: ignore
    nodemgr._load.assert_called_once()  # type: ignore
    nodemgr._incoming_msg_task.assert_called_once()  # type: ignore
    nodemgr._connmgr.start_receiving.assert_called_once()  # type: ignore
async def test_assistant(
    mocker: MockerFixture,
    root: Path,
    config: Config,
    post: Post,
) -> None:
    """
    Test the assistant.
    """
    mocker.patch(
        "nefelibata.assistants.mirror_images.download_image",
        return_value=mocker.AsyncMock(),
    )

    assistant = MirrorImagesAssistant(root, config)

    post.content = """
I have 2 images:

- ![First image](https://example.com/photo.jpg)
- ![Second image](img/logo.png)
    """

    await assistant.process_post(post)
    await assistant.process_post(post)

    async def modify_replacements(  # pylint: disable=too-many-arguments, unused-argument
        session: ClientSession,
        url: str,
        title: str,
        post: Post,
        mirror: Path,
        replacements: Dict[str, str],
    ) -> None:
        """
        A dummy function to modify ``replacements``.
        """
        replacements["hello"] = "world"

    mocker.patch(
        "nefelibata.assistants.mirror_images.download_image",
        modify_replacements,
    )

    await assistant.process_post(post)
async def test_get_webmention_endpoint_unicode_error(
        mocker: MockerFixture) -> None:
    """
    Test ``get_webmention_endpoint`` when response is not text.
    """
    session = mocker.MagicMock()
    head_response = session.head.return_value.__aenter__.return_value
    head_response.headers = {
        "content-type": "text/html",
    }
    head_response.links = {}
    mocker.patch("nefelibata.announcers.webmention.UnicodeDecodeError",
                 Exception)
    get_response = session.get.return_value.__aenter__.return_value
    get_response.text = mocker.AsyncMock()
    get_response.text.side_effect = Exception()

    endpoint = await get_webmention_endpoint(
        session,
        URL("https://example.com/post/hello.php"),
    )
    assert endpoint is None
Beispiel #17
0
async def test_announcer_announce(
    mocker: MockerFixture,
    root: Path,
    config: Config,
    post: Post,
) -> None:
    """
    Test announcing sites and posts.
    """
    gemini_builder = Builder(root, config, "gemini://example.com/", "gemini")
    html_builder = Builder(root, config, "https://example.com/", "www")

    Client = mocker.patch("nefelibata.announcers.gemlog.Client")
    Client.return_value.get = mocker.AsyncMock()

    announcer = CAPCOMAnnouncer(root, config, builders=[gemini_builder])

    # post announcement is no-op
    announcement = await announcer.announce_post(post)
    assert announcement is None

    with freeze_time("2021-01-01T00:00:00Z"):
        announcement = await announcer.announce_site()
    assert announcement.dict() == {
        "url": "gemini://gemini.circumlunar.space/capcom/",
        "timestamp": datetime(2021, 1, 1, 0, 0, tzinfo=timezone.utc),
        "grace_seconds": 31536000,
    }
    assert Client.return_value.get.called_with(
        URL(
            "gemini://gemini.circumlunar.space/capcom/submit?gemini://example.com/feed",
        ), )

    announcer = CAPCOMAnnouncer(root, config, builders=[html_builder])
    with pytest.raises(Exception) as excinfo:
        await announcer.announce_site()
    assert str(
        excinfo.value) == "CAPCOM announcer only works with `gemini://` builds"
Beispiel #18
0
async def test_main_new(mocker: MockerFixture) -> None:
    """
    Test ``main`` with the "new" action.
    """
    new = mocker.patch("nefelibata.console.new")
    new.run = mocker.AsyncMock()

    mocker.patch(
        "nefelibata.console.docopt",
        return_value={
            "--loglevel": "debug",
            "init": False,
            "new": True,
            "build": False,
            "publish": False,
            "ROOT_DIR": "/path/to/blog",
            "POST": "A like",
            "-t": "like",
            "--force": False,
        },
    )
    await console.main()
    new.run.assert_called_with(Path("/path/to/blog"), "A like", "like")
Beispiel #19
0
async def test_main_canceled(mocker: MockerFixture) -> None:
    """
    Test canceling the ``main`` coroutine.
    """
    build = mocker.patch("nefelibata.console.build")
    build.run = mocker.AsyncMock(
        side_effect=asyncio.CancelledError("Canceled"))
    _logger = mocker.patch("nefelibata.console._logger")

    mocker.patch(
        "nefelibata.console.docopt",
        return_value={
            "--loglevel": "debug",
            "init": False,
            "new": False,
            "build": True,
            "publish": False,
            "ROOT_DIR": "/path/to/blog",
            "--force": False,
        },
    )
    await console.main()

    _logger.info.assert_called_with("Canceled")
Beispiel #20
0
async def test_run(
    mocker: MockerFixture,
    root: Path,
    post: Post,
) -> None:
    """
    Test ``run``.
    """
    assistant = mocker.MagicMock()
    assistant.process_post = mocker.AsyncMock()
    assistant.process_site = mocker.AsyncMock()

    builder = mocker.MagicMock()
    builder.process_post = mocker.AsyncMock()
    builder.process_site = mocker.AsyncMock()

    announcer1 = mocker.MagicMock()
    announcer1.collect_post = mocker.AsyncMock(return_value={})
    announcer1.collect_site = mocker.AsyncMock(return_value={})

    announcer2 = mocker.MagicMock()
    announcer2.collect_site = mocker.AsyncMock(return_value={})
    announcer2.collect_site.return_value = {}

    mocker.patch(
        "nefelibata.cli.build.get_announcers",
        return_value={
            "announcer1": announcer1,
            "announcer2": announcer2
        },
    )
    mocker.patch(
        "nefelibata.cli.build.get_assistants",
        return_value={"assistant": assistant},
    )
    mocker.patch("nefelibata.cli.build.get_builders",
                 return_value={"builder": builder})
    mocker.patch("nefelibata.cli.build.get_config")
    mocker.patch("nefelibata.cli.build.get_posts", return_value=[post])

    _logger = mocker.patch("nefelibata.cli.build._logger")

    post.announcers = {"announcer1"}

    await build.run(root)

    assistant.process_post.assert_called_with(post, False)
    assistant.process_site.assert_called_with(False)
    builder.process_post.assert_called_with(post, False)
    builder.process_site.assert_called_with(False)
    announcer1.collect_post.assert_called_with(post)
    announcer1.collect_site.assert_called_with()
    announcer2.collect_post.assert_not_called()
    announcer2.collect_site.assert_called_with()
    _logger.info.assert_has_calls([
        mocker.call("Building blog"),
        mocker.call("Creating `build/` directory"),
        mocker.call("Collecting interactions from posts"),
        mocker.call("Collecting interactions from site"),
        mocker.call("Running post assistants"),
        mocker.call("Running site assistants"),
        mocker.call("Processing posts"),
        mocker.call("Processing site"),
    ], )

    _logger.reset_mock()

    await build.run(root)
    _logger.info.assert_has_calls([
        mocker.call("Processing posts"),
        mocker.call("Processing site"),
    ], )
async def test_run(
    mocker: MockerFixture,
    root: Path,
    config: Config,
    post: Post,
) -> None:
    """
    Test ``publish``.
    """
    publisher = mocker.MagicMock()
    publisher.publish = mocker.AsyncMock(side_effect=[
        Publishing(timestamp=datetime(2021, 1, 1)),
        None,
        Publishing(timestamp=datetime(2021, 1, 3)),
    ], )

    announcer = mocker.MagicMock()
    announcer.announce_post = mocker.AsyncMock(side_effect=[
        None,
        Announcement(
            url="https://host1.example.com/",
            timestamp=datetime(2021, 1, 1),
        ),
    ], )
    announcer.announce_site = mocker.AsyncMock(side_effect=[
        Announcement(
            url="https://host2.example.com/",
            timestamp=datetime(2021, 1, 2),
        ),
        None,
    ], )

    mocker.patch(
        "nefelibata.cli.publish.get_announcers",
        return_value={"announcer": announcer},
    )
    mocker.patch(
        "nefelibata.cli.publish.get_publishers",
        return_value={"publisher": publisher},
    )

    # On the first publish we should announce site and post.
    await publish.run(root)
    publisher.publish.assert_called_with(None, False)
    announcer.announce_site.assert_called_with()
    announcer.announce_post.assert_called()

    # Publish again, should have a ``since`` value. Because ``publish``
    # returns ``None`` the second time we shouldn't announce the site.
    # And because the first time ``publish_post`` returned ``None``,
    # it should try again now and be called.
    announcer.announce_site.reset_mock()
    announcer.announce_post.reset_mock()
    await publish.run(root)
    publisher.publish.assert_called_with(datetime(2021, 1, 1), False)
    announcer.announce_site.assert_not_called()
    announcer.announce_post.assert_called()

    # Publish again. This time ``publish`` returns a new data, so we
    # expect to call ``publish_site``. Because the post was already
    # published last time it shouldn't be announced this time.
    announcer.announce_post.reset_mock()
    await publish.run(root)
    publisher.publish.assert_called_with(datetime(2021, 1, 1), False)
    announcer.announce_site.assert_called_with()
    announcer.announce_post.assert_not_called()
Beispiel #22
0
def test_task(
    event_loop: asyncio.AbstractEventLoop,
    log_records: LogRecordsType,
    mocker: MockerFixture,
    opcua_client: OPCUAClient,
    subscription_success: bool,
) -> None:
    mocked_client = MagicMock()
    mocker.patch.object(opcua_client,
                        "_create_opc_client",
                        new=AsyncMock(return_value=mocked_client))
    mocker.patch("opcua_webhmi_bridge.opcua.UaStatusCodeError",
                 FakeUaStatusCodeError)
    type_node = mocker.sentinel.type_node
    mocked_client.get_namespace_index = mocker.AsyncMock(
        return_value=mocker.sentinel.ns)
    mocked_client.nodes.opc_binary.get_child = mocker.AsyncMock(
        return_value=type_node)
    mocked_client.load_type_definitions = mocker.AsyncMock()
    mocked_client.create_subscription = mocker.AsyncMock()
    subscription = mocked_client.create_subscription.return_value
    sub_results = [12, 34, 56, 78, 910]
    if not subscription_success:
        sub_results[-2] = mocker.Mock(
            **{"check.side_effect": FakeUaStatusCodeError})
    subscription.subscribe_data_change = mocker.AsyncMock(
        return_value=sub_results)
    mocked_sleep: AsyncMock = mocker.patch("asyncio.sleep")
    gotten_node = mocked_client.get_node.return_value
    read_data_value = gotten_node.read_data_value = mocker.AsyncMock(
        side_effect=InfiniteLoopBreaker)
    cm: ContextManager[Any]
    if subscription_success:
        cm = contextlib.suppress(InfiniteLoopBreaker)
    else:
        cm = pytest.raises(FakeUaStatusCodeError)
    with cm:
        event_loop.run_until_complete(opcua_client._task())
    assert mocked_client.__aenter__.await_count == 1
    assert mocked_client.get_namespace_index.await_args_list == [
        mocker.call(SIMATIC_NAMESPACE_URI)
    ]
    assert mocked_client.nodes.opc_binary.get_child.await_args_list == [
        mocker.call("sentinel.ns:SimaticStructures")
    ]
    assert mocked_client.load_type_definitions.await_args_list == [
        mocker.call([type_node])
    ]
    get_node = cast(Mock, mocked_client.get_node)
    expected_get_node_calls = [
        mocker.call("ns=sentinel.ns;s=monitornode1"),
        mocker.call("ns=sentinel.ns;s=monitornode2"),
        mocker.call("ns=sentinel.ns;s=recnode1"),
        mocker.call("ns=sentinel.ns;s=recnode2"),
    ]
    if subscription_success:
        expected_get_node_calls.append(mocker.call(2259))
    assert get_node.call_args_list == expected_get_node_calls
    assert mocked_client.create_subscription.await_args_list == [
        mocker.call(1000, opcua_client)
    ]
    assert subscription.subscribe_data_change.await_args_list == [
        mocker.call([gotten_node, gotten_node, gotten_node, gotten_node])
    ]
    if subscription_success:
        assert mocked_sleep.await_args_list == [mocker.call(5)]
        assert read_data_value.await_args_list == [mocker.call()]
    else:
        last_log_record = log_records()[-1]
        assert last_log_record.levelno == logging.ERROR
        assert "Error subscribing to node" in last_log_record.message
Beispiel #23
0
 def __init__(self, mocker: pytest_mock.MockerFixture) -> None:
     self.get_passage = mocker.AsyncMock()
     self.search = mocker.AsyncMock()
async def test_announcer_announce(
    mocker: MockerFixture,
    root: Path,
    config: Config,
    post: Post,
) -> None:
    """
    Test announcing posts.
    """
    gemini_builder = Builder(root, config, "gemini://example.com/", "gemini")
    gemini_builder.extension = ".gmi"
    html_builder = Builder(root, config, "https://example.com/", "www")
    html_builder.extension = ".html"

    send_webmention = mocker.AsyncMock(side_effect=[
        Webmention(
            source="gemini://example.com/first/index.html",
            target="https://nefelibata.readthedocs.io/",
            status="invalid",
        ),
        Webmention(
            source="https://example.com/first/index.html",
            target="https://nefelibata.readthedocs.io/",
            status="queue",
            location="https://bob.example.com/webmention.php?id=42",
        ),
    ], )
    mocker.patch("nefelibata.announcers.webmention.send_webmention",
                 send_webmention)

    announcer = WebmentionAnnouncer(
        root,
        config,
        builders=[gemini_builder, html_builder],
    )

    announcement = await announcer.announce_post(post)
    assert announcement is None

    path = post.path.parent / "webmentions.yaml"
    with open(path, encoding="utf-8") as input_:
        webmentions = yaml.load(input_, Loader=yaml.SafeLoader)
    assert webmentions == {
        "gemini://example.com/first/index.gmi => https://nefelibata.readthedocs.io/":
        {
            "location": None,
            "source": "gemini://example.com/first/index.html",
            "status": "invalid",
            "target": "https://nefelibata.readthedocs.io/",
        },
        "https://example.com/first/index.html => https://nefelibata.readthedocs.io/":
        {
            "location": "https://bob.example.com/webmention.php?id=42",
            "source": "https://example.com/first/index.html",
            "status": "queue",
            "target": "https://nefelibata.readthedocs.io/",
        },
    }

    update_webmention = mocker.AsyncMock(return_value=Webmention(
        source="https://example.com/first/index.html",
        target="https://nefelibata.readthedocs.io/",
        status="success",
        location=None,
    ), )
    mocker.patch(
        "nefelibata.announcers.webmention.update_webmention",
        update_webmention,
    )

    with freeze_time("2021-01-01T00:00:00Z"):
        announcement = await announcer.announce_post(post)
    assert announcement == Announcement(
        url="first/index",
        timestamp=datetime(2021, 1, 1, tzinfo=timezone.utc),
        grace_seconds=0,
    )

    path = post.path.parent / "webmentions.yaml"
    with open(path, encoding="utf-8") as input_:
        webmentions = yaml.load(input_, Loader=yaml.SafeLoader)
    assert webmentions == {
        "gemini://example.com/first/index.gmi => https://nefelibata.readthedocs.io/":
        {
            "location": None,
            "source": "gemini://example.com/first/index.html",
            "status": "invalid",
            "target": "https://nefelibata.readthedocs.io/",
        },
        "https://example.com/first/index.html => https://nefelibata.readthedocs.io/":
        {
            "location": None,
            "source": "https://example.com/first/index.html",
            "status": "success",
            "target": "https://nefelibata.readthedocs.io/",
        },
    }
Beispiel #25
0
async def test_enable(
    gstate: GlobalState,
    fs: fake_filesystem.FakeFilesystem,
    mocker: MockerFixture,
) -> None:

    from gravel.controllers.nodes.systemdisk import (
        MountError,
        OverlayError,
        SystemDisk,
    )

    async def mount_fail() -> None:
        raise MountError("Failed mount.")

    overlayed_paths = []
    bindmounts = []

    async def mock_call(
        cmd: List[str],
    ) -> Tuple[int, Optional[str], Optional[str]]:

        if cmd[2] == "overlay":
            assert "lower" in cmd[4]
            lowerstr = (cmd[4].split(","))[0]
            lower = (lowerstr.split("="))[1]
            overlayed_paths.append(lower)
        elif cmd[1] == "--bind":
            assert len(cmd) == 4
            bindmounts.append(cmd[3])
        else:
            raise Exception(f"Unknown call: {cmd}")
        return 0, None, None

    # ensure we don't have a mounted fs
    fs.add_real_file(
        source_path=os.path.join(DATA_DIR, "mounts_without_aquarium.raw"),
        target_path="/proc/mounts",
    )

    systemdisk = SystemDisk(gstate)
    assert not systemdisk.mounted
    systemdisk.mount = mount_fail

    mocker.patch(
        "gravel.controllers.nodes.systemdisk.aqr_run_cmd", new=mock_call
    )

    throws = False
    try:
        await systemdisk.enable()
    except OverlayError as e:
        assert "failed mount" in e.message.lower()
        throws = True
    assert throws

    systemdisk.mount = mocker.AsyncMock()

    for upper in systemdisk._overlaydirs.keys():
        fs.create_dir(f"/var/lib/aquarium-system/{upper}/overlay")
        fs.create_dir(f"/var/lib/aquarium-system/{upper}/temp")

    for ours in systemdisk._bindmounts.keys():
        fs.create_dir(f"/var/lib/aquarium-system/{ours}")

    await systemdisk.enable()

    for lower in systemdisk._overlaydirs.values():
        assert fs.exists(lower)
        assert lower in overlayed_paths

    for theirs in systemdisk._bindmounts.values():
        assert fs.exists(theirs)
        assert theirs in bindmounts
async def test_get_webmention_endpoint(mocker: MockerFixture) -> None:
    """
    Test ``get_webmention_endpoint``.
    """
    session = mocker.MagicMock()
    head_response = session.head.return_value.__aenter__.return_value
    head_response.headers = {
        "content-type": "text/html",
    }
    head_response.links = {
        "alternate": {
            "url": "/atom.xml",
        },
        "webmention": {
            "url": "/webmention.php",
        },
    }
    get_response = session.get.return_value.__aenter__.return_value
    get_response.text = mocker.AsyncMock()
    get_response.text.return_value = """<!doctype html>
<html lang="en">
  <head>
    <link rel="webmention" href="/webmention.php" />
  </head>
  <body>
  </body>
</html>"""

    # endpoint in Link header
    endpoint = await get_webmention_endpoint(
        session,
        URL("https://example.com/post/hello.php"),
    )
    assert endpoint == URL("https://example.com/webmention.php")

    # endpoint in HTML
    head_response.links = {}
    endpoint = await get_webmention_endpoint(
        session,
        URL("https://example.com/post/hello.php"),
    )
    assert endpoint == URL("https://example.com/webmention.php")

    # no endpoint in HTML
    get_response.text.return_value = """<!doctype html>
<html lang="en">
  <head>
  </head>
  <body>
  </body>
</html>"""
    endpoint = await get_webmention_endpoint(
        session,
        URL("https://example.com/post/hello.php"),
    )
    assert endpoint is None

    # unable to extract endpoint
    head_response.headers = {
        "content-type": "application/pdf",
    }
    endpoint = await get_webmention_endpoint(
        session,
        URL("https://example.com/post/hello.php"),
    )
    assert endpoint is None

    # gemini target
    head_response.links = {}
    endpoint = await get_webmention_endpoint(
        session,
        URL("gemini://example.com/post/hello.gmi"),
    )
    assert endpoint is None
Beispiel #27
0
async def test_announcer_collect(
    mocker: MockerFixture,
    root: Path,
    config: Config,
    post: Post,
) -> None:
    """
    Test collecting interactions on posts and sites.
    """
    builder = Builder(root, config, "gemini://example.com/", "gemini")

    Client = mocker.patch("nefelibata.announcers.gemlog.Client")
    Client.return_value.get = mocker.AsyncMock()
    Client.return_value.get.return_value.read.side_effect = [
        b"""
=> gemini://example.com/reply.gmi Re: This is your first post
    """,
        b"""
=> gemini://example.com/first/index.gmi
    """,
    ]

    announcer = CAPCOMAnnouncer(root, config, builders=[builder])

    # post collect is no-op
    interactions = await announcer.collect_post(post)
    assert interactions == {}

    interactions = await announcer.collect_site()
    assert interactions == {
        Path("/path/to/blog/posts/first/index.mkd"): {
            "reply,gemini://example.com/reply.gmi":
            Interaction(
                id="reply,gemini://example.com/reply.gmi",
                name="Re: This is your first post",
                url="gemini://example.com/reply.gmi",
                type="reply",
                timestamp=None,
            ),
        },
    }

    # test no backlink
    Client.return_value.get.return_value.read.side_effect = [
        b"""
=> gemini://example.com/reply.gmi Re: This is your first post
    """,
        b"",
    ]

    announcer = CAPCOMAnnouncer(root, config, builders=[builder])

    interactions = await announcer.collect_site()
    assert interactions == {}

    # test SSL error
    response = mocker.AsyncMock()
    response.read.return_value = b"""
=> gemini://example.com/reply.gmi Re: This is your first post
    """
    Client.return_value.get.side_effect = [
        response,
        ssl.SSLCertVerificationError("A wild error appears!"),
    ]

    announcer = CAPCOMAnnouncer(root, config, builders=[builder])

    interactions = await announcer.collect_site()
    assert interactions == {
        Path("/path/to/blog/posts/first/index.mkd"): {
            "reply,gemini://example.com/reply.gmi":
            Interaction(
                id="reply,gemini://example.com/reply.gmi",
                name="Re: This is your first post",
                url="gemini://example.com/reply.gmi",
                type="reply",
                timestamp=None,
            ),
        },
    }
async def test_announcer_collect(
    mocker: MockerFixture,
    root: Path,
    config: Config,
    post: Post,
) -> None:
    """
    Test collecting posts.
    """
    gemini_builder = Builder(root, config, "gemini://example.com/", "gemini")
    gemini_builder.extension = ".gmi"
    html_builder = Builder(root, config, "https://example.com/", "www")
    html_builder.extension = ".html"

    mocker.patch("nefelibata.announcers.webmention.ClientResponseError",
                 Exception)
    get = mocker.patch("nefelibata.announcers.webmention.ClientSession.get")
    get_response = get.return_value.__aenter__.return_value
    get_response.raise_for_status = mocker.MagicMock()
    get_response.raise_for_status.side_effect = [
        Exception("Gemini not supported"),
        None,
        Exception("Gemini not supported"),
        None,
    ]

    announcer = WebmentionAnnouncer(
        root,
        config,
        builders=[gemini_builder, html_builder],
    )

    get_response.json = mocker.AsyncMock(return_value={"children": []})
    interactions = await announcer.collect_post(post)
    assert interactions == {}

    entries = [
        {
            "type": "entry",
            "wm-id": 1,
            "wm-source": "https://alice.example.com/posts/one",
            "name": "This is the title",
            "summary": {
                "value": "This is the summary"
            },
            "content": {
                "text": "This is the post content."
            },
            "published": "2021-01-01T00:00:00Z",
            "author": {
                "name": "Alice Doe",
                "url": "https://alice.example.com/",
                "photo": "https://alice.example.com/photo.jpg",
                "note": "My name is Alice",
            },
            "wm-property": "in-reply-to",
        },
        {
            "type": "entry",
            "wm-id": 2,
            "wm-source": "https://bob.example.com/posts/two",
            "summary": {
                "value": "This is the summary"
            },
            "content": {
                "text": "This is the post content."
            },
            "published": "2021-01-01T00:00:00Z",
            "author": {
                "name": "Bob Doe",
                "url": "https://bob.example.com/",
                "photo": "https://bob.example.com/photo.jpg",
            },
            "wm-property": "like-of",
        },
        {
            "type": "invalid"
        },
    ]

    get_response.json = mocker.AsyncMock(return_value={"children": entries})
    interactions = await announcer.collect_post(post)
    assert interactions == {
        1:
        Interaction(
            id="1",
            name="This is the title",
            summary="This is the summary",
            content="This is the post content.",
            published=datetime(2021, 1, 1, 0, 0, tzinfo=timezone.utc),
            updated=None,
            author=Author(
                name="Alice Doe",
                url="https://alice.example.com/",
                avatar="https://alice.example.com/photo.jpg",
                note="My name is Alice",
            ),
            url="https://alice.example.com/posts/one",
            in_reply_to=None,
            type="reply",
        ),
        2:
        Interaction(
            id="2",
            name="https://bob.example.com/posts/two",
            summary="This is the summary",
            content="This is the post content.",
            published=datetime(2021, 1, 1, 0, 0, tzinfo=timezone.utc),
            updated=None,
            author=Author(
                name="Bob Doe",
                url="https://bob.example.com/",
                avatar="https://bob.example.com/photo.jpg",
                note="",
            ),
            url="https://bob.example.com/posts/two",
            in_reply_to=None,
            type="like",
        ),
    }
Beispiel #29
0
async def test_join(gstate: GlobalState, mocker: MockerFixture,
                    nodemgr: NodeMgr) -> None:

    from uuid import UUID

    from gravel.controllers.inventory.disks import DiskDevice
    from gravel.controllers.nodes.deployment import DeploymentDisksConfig
    from gravel.controllers.nodes.disks import DiskSolution
    from gravel.controllers.nodes.mgr import JoinParamsModel, NodeInitStage

    def mock_solution(gstate: GlobalState) -> DiskSolution:
        return DiskSolution(
            systemdisk=DiskDevice(
                id="foo01",
                name="foo",
                path="/dev/foo",
                product="Foo",
                vendor="Foo Inc",
                size=1000,
                rotational=False,
                available=True,
                rejected_reasons=[],
            ),
            storage=[
                DiskDevice(
                    id="bar01",
                    name="bar",
                    path="/dev/bar",
                    product="Bar",
                    vendor="Bar LLC",
                    size=2000,
                    rotational=False,
                    available=True,
                    rejected_reasons=[],
                ),
                DiskDevice(
                    id="baz01",
                    name="baz",
                    path="/dev/baz",
                    product="Baz",
                    vendor="Baz Ltd",
                    size=2000,
                    rotational=False,
                    available=True,
                    rejected_reasons=[],
                ),
            ],
            storage_size=4000,
            possible=True,
        )

    async def mock_join(
        leader_address: str,
        token: str,
        uuid: UUID,
        hostname: str,
        address: str,
        disks: DeploymentDisksConfig,
    ) -> bool:
        assert leader_address == "10.1.2.3"
        assert token == "751b-51fd-10d7-f7b4"
        assert str(uuid) == "bba35d93-d4a5-48b3-804b-99c406555c89"
        assert hostname == "foobar"
        assert address == "1.2.3.4"
        assert disks.system == "/dev/foo"
        assert len(disks.storage) == 2
        assert "/dev/bar" in disks.storage
        assert "/dev/baz" in disks.storage
        return True

    mocker.patch("gravel.controllers.nodes.disks.Disks.gen_solution",
                 new=mock_solution)

    nodemgr._init_stage = NodeInitStage.AVAILABLE

    nodemgr._deployment.join = mocker.AsyncMock(side_effects=Exception())
    throws = False
    try:
        await nodemgr.join(
            "10.1.2.3",
            "751b-51fd-10d7-f7b4",
            JoinParamsModel(hostname="foobar"),
        )
    except Exception:
        throws = True
    assert throws
    nodemgr._deployment.join.assert_called_once()  # type: ignore

    nodemgr._deployment.join = mocker.AsyncMock(return_value=False)
    res = await nodemgr.join("10.1.2.3", "751b-51fd-10d7-f7b4",
                             JoinParamsModel(hostname="foobar"))
    assert not res
    nodemgr._deployment.join.assert_called_once()  # type: ignore

    nodemgr._save_state = mocker.AsyncMock()
    nodemgr._node_start = mocker.AsyncMock()
    nodemgr._deployment.join = mock_join
    res = await nodemgr.join("10.1.2.3", "751b-51fd-10d7-f7b4",
                             JoinParamsModel(hostname="foobar"))
    assert res
    assert nodemgr._token == "751b-51fd-10d7-f7b4"
    nodemgr._save_state.assert_called_once()  # type: ignore
    nodemgr._node_start.assert_called_once()  # type: ignore
Beispiel #30
0
async def test_deploy(gstate: GlobalState, mocker: MockerFixture,
                      nodemgr: NodeMgr) -> None:

    from gravel.controllers.auth import UserMgr, UserModel
    from gravel.controllers.inventory.disks import DiskDevice
    from gravel.controllers.nodes.deployment import DeploymentConfig
    from gravel.controllers.nodes.disks import DiskSolution
    from gravel.controllers.nodes.mgr import NodeInitStage

    called_mock_deploy = False

    def mock_solution(gstate: GlobalState) -> DiskSolution:
        return DiskSolution(
            systemdisk=DiskDevice(
                id="foo01",
                name="foo",
                path="/dev/foo",
                product="Foo",
                vendor="Foo Inc",
                size=1000,
                rotational=False,
                available=True,
                rejected_reasons=[],
            ),
            storage=[
                DiskDevice(
                    id="bar01",
                    name="bar",
                    path="/dev/bar",
                    product="Bar",
                    vendor="Bar LLC",
                    size=2000,
                    rotational=False,
                    available=True,
                    rejected_reasons=[],
                ),
                DiskDevice(
                    id="baz01",
                    name="baz",
                    path="/dev/baz",
                    product="Baz",
                    vendor="Baz Ltd",
                    size=2000,
                    rotational=False,
                    available=True,
                    rejected_reasons=[],
                ),
            ],
            storage_size=4000,
            possible=True,
        )

    async def mock_deploy(
        config: DeploymentConfig,
        post_bootstrap_cb: Callable[[bool, Optional[str]], Awaitable[None]],
        finisher: Callable[[bool, Optional[str]], Awaitable[None]],
    ) -> None:

        import inspect

        nonlocal called_mock_deploy
        called_mock_deploy = True

        assert config.hostname == "barbaz"
        assert config.address == "1.2.3.4"
        assert config.token == "751b-51fd-10d7-f7b4"
        assert config.ntp_addr == "my.ntp.addr"
        assert config.disks.system == "/dev/foo"
        assert len(config.disks.storage) == 2
        assert "/dev/bar" in config.disks.storage
        assert "/dev/baz" in config.disks.storage
        assert post_bootstrap_cb is not None
        assert finisher is not None
        assert inspect.iscoroutinefunction(post_bootstrap_cb)
        assert inspect.iscoroutinefunction(finisher)

    mocker.patch("gravel.controllers.nodes.disks.Disks.gen_solution",
                 new=mock_solution)

    nodemgr._init_stage = NodeInitStage.AVAILABLE

    nodemgr._generate_token = mocker.MagicMock(
        return_value="751b-51fd-10d7-f7b4")
    nodemgr._save_token = mocker.AsyncMock()
    nodemgr._deployment.deploy = mock_deploy

    await gstate.store.ensure_connection()

    await nodemgr.deploy(
        DeployParamsModel(hostname="barbaz", ntpaddr="my.ntp.addr"))

    assert called_mock_deploy
    assert nodemgr._token == "751b-51fd-10d7-f7b4"
    assert nodemgr._state.hostname == "barbaz"
    nodemgr._save_token.assert_called_once()  # type: ignore

    ntpaddr = await gstate.store.get("/nodes/ntp_addr")
    assert ntpaddr == "my.ntp.addr"

    usermgr = UserMgr(gstate.store)
    assert await usermgr.exists("admin")
    user: Optional[UserModel] = await usermgr.get("admin")
    assert user is not None
    assert user.username == "admin"
    # We can't test the plain password here because it will fail
    # and we don't care particularly about the password itself, just that
    # the user has been populated. We'll leave for the 'UserMgr' tests to
    # validate the correctness of its operations.
    assert len(user.password) > 0