rustploy-gui 0.1.13

Rustploy — desktop client (glacier-ui) for the Rustploy PaaS daemon
--!nonstrict
-- Toda chave que uma tela LÊ tem de ser publicada por alguém. `tela:missing()`
-- devolve as que o markup lê e o `ctx` não tem; handler que escreve `total` e
-- markup que lê `{totall}` passa em todo teste de handler e deixa a tela vazia.
--
-- O que sobra na lista de cada tela é conhecido e tem causa. Os conjuntos abaixo
-- são o contrato: o teste falha se uma chave NOVA aparecer (alguém leu algo que
-- ninguém publica) e também se uma ESPERADA sumir (passou a ser publicada no
-- init, ou deixou de ser lida — atualize a lista).
--
-- LIMITE: o snapshot do daemon chega por SSE (stream.luau::apply_snapshot é
-- local e só roda lá), então não dá para publicá-lo aqui. As chaves "do
-- snapshot" abaixo são só CONGELADAS: este teste prova que o markup depende
-- delas, não que o stream as escreve com o mesmo nome.

local MOTOR = { "glacier_window_maximized" } -- o motor publica o estado da janela

-- Publicadas em connect()/apply_snapshot, depois do login.
local SESSAO = { "connected_server", "daemon_version" }

local function conjunto(...)
    local c = {}
    for _, lista in { ... } do
        for _, k in lista do
            c[k] = true
        end
    end
    return c
end

local function confere(tela, ...)
    local esperado = conjunto(...)
    local obtido = conjunto(tela:missing())
    local novas, sumidas = {}, {}
    for k in obtido do
        if not esperado[k] then
            table.insert(novas, k)
        end
    end
    for k in esperado do
        if not obtido[k] then
            table.insert(sumidas, k)
        end
    end
    table.sort(novas)
    table.sort(sumidas)
    -- Duas asserções para a mensagem de falha nomear as chaves.
    expect(novas):toEqual({}) -- lidas pelo markup e que ninguém publica
    expect(sumidas):toEqual({}) -- esperadas, mas já publicadas (ou não lidas): atualize a lista
end

local function app(visao)
    -- access_key semeado: o init pré-preencheria com o servidor salvo da máquina.
    local tela = component("views/app.gvb", { access_key = "x" })
    tela:call("init")
    if visao then
        tela:set("screen", "shell")
        tela:set("view", visao)
    end
    return tela
end

describe("login", function()
    test("a tela de login só espera o erro opcional e o estado da janela", function()
        -- error_access_key só é escrita quando o conectar falha; `if=@error_access_key`.
        confere(app(), MOTOR, { "error_access_key" })
    end)
end)

describe("shell · cada visão", function()
    test("deployments", function()
        confere(app("deployments"), MOTOR, SESSAO, {
            "daemon_uptime", "services_label", -- snapshot
            "deployments_message", -- só depois de "limpar finalizados"
            "deployments_sort", -- a tabela grava ao ordenar
        })
    end)

    test("projects", function()
        confere(app("projects"), MOTOR, SESSAO)
    end)

    test("project_services", function()
        confere(app("project_services"), MOTOR, SESSAO, {
            "editing_project", "project_action_message", "project_can_delete",
            "project_description", "project_loading", "project_name", "project_tab",
        })
    end)

    test("service", function()
        confere(app("service"), MOTOR, SESSAO, {
            "daemon_uptime", "services_label",
            "service_action_message", "service_can_migrate", "service_deploy_running",
            "service_error", "service_name", "service_project", "service_project_id",
            "service_shared", "service_status_color", "service_status_label",
            "service_warm_visible", "tab",
        })
    end)

    test("running", function()
        confere(app("running"), MOTOR, SESSAO, { "running_count" })
    end)

    test("monitoring", function()
        confere(app("monitoring"), MOTOR, SESSAO, { "host_cpu", "host_disk", "host_load", "host_memory", "monitoring_sort" })
    end)

    test("ingress", function()
        confere(app("ingress"), MOTOR, SESSAO, { "host_ports_sort", "ingress_sort" })
    end)

    test("deploy_engine", function()
        confere(app("deploy_engine"), MOTOR, SESSAO, { "deploy_engine_queued_count", "deploy_engine_tab", "deploy_engine_uptime" })
    end)

    test("docker", function()
        confere(app("docker"), MOTOR, SESSAO, { "docker_containers_count", "docker_tab" })
    end)

    test("settings", function()
        confere(app("settings"), MOTOR, SESSAO, { "settings_tab" })
    end)

    test("schedules", function()
        confere(app("schedules"), MOTOR, SESSAO, { "jobs_sort" })
    end)

    test("support", function()
        confere(app("support"), MOTOR, SESSAO)
    end)
end)

describe("janelas auxiliares", function()
    local function janela(caminho, ctx_inicial)
        local tela = component(caminho, ctx_inicial)
        tela:call("init")
        return tela
    end

    test("logs ao vivo", function()
        confere(janela("views/log_window.gvb"), MOTOR)
    end)

    test("novo job", function()
        confere(janela("views/new_job_window.gvb"), {})
    end)

    test("novo serviço (com o projeto-alvo semeado pelo open_window)", function()
        confere(janela("views/new_service_window.gvb", { project_name = "loja", new_service_templates = "[]" }), {})
    end)

    test("novo projeto", function()
        -- O motor lê `erro_<campo>` de TODO form_control, mesmo sem `rules` (aqui
        -- a descrição é opcional) — uma leitura interna, não esquecimento do app.
        -- error_new_project_name só existe depois de uma validação que falha.
        confere(janela("views/new_project_form.gvb"), { "erro_new_project_description", "error_new_project_name" })
    end)

    test("novo token do registry", function()
        confere(janela("views/new_registry_token_window.gvb"), { "error_new_registry_token_name" })
    end)

    test("exportar serviço", function()
        confere(janela("views/service_export_window.gvb", { export_plan = "{}" }), {})
    end)
end)