rustploy-gui 0.1.13

Rustploy — desktop client (glacier-ui) for the Rustploy PaaS daemon
--!nonstrict
local Git = require("format/git")
local Registry = require("format/registry")
local DockerCleanup = require("format/docker_cleanup")

describe("format/git", function()
    test("provedor conectado mostra o @login", function()
        local rows = json.decode(Git.git_providers({
            { id = "p1", name = "gh", kind = "Github", base_url = "https://github.com", auth_mode = "OAuth", account = { login = "ana" } },
        }))
        expect(#rows):toBe(1)
        expect(rows[1].display):toBe("gh (@ana)")
        expect(rows[1].account):toBe("@ana")
        expect(rows[1].connected):toBe("true")
        expect(rows[1].kind):toBe("GitHub")
        expect(rows[1].auth_mode):toBe("OAuth2")
    end)

    test("provedor sem conta diz que não está conectado", function()
        local rows = json.decode(Git.git_providers({
            { id = "p2", name = "meu-gitea", kind = "Gitea", auth_mode = "Pat" },
        }))
        expect(rows[1].connected):toBe("false")
        expect(rows[1].display):toBe("meu-gitea — não conectado")
        expect(rows[1].account_login):toBe("não conectado")
        expect(rows[1].auth_mode):toBe("PAT")
        expect(rows[1].kind):toBe("Gitea")
    end)

    test("tipo desconhecido aparece como veio, e ausente como Git", function()
        local rows = json.decode(Git.git_providers({ { name = "a", kind = "Gitlab" }, { name = "b" } }))
        expect(rows[1].kind):toBe("Gitlab")
        expect(rows[2].kind):toBe("Git")
    end)

    test("listas vazias viram [] e não {}", function()
        expect(Git.git_providers(nil)):toBe("[]")
        expect(Git.git_repos({})):toBe("[]")
        expect(Git.git_branches(nil)):toBe("[]")
    end)

    test("repositórios marcam private/public e branches só carregam o nome", function()
        local repos = json.decode(Git.git_repos({
            { full_name = "a/b", clone_url = "u", default_branch = "main", private = true },
            { full_name = "a/c", clone_url = "v", default_branch = "dev" },
        }))
        expect(repos[1].private):toBe("private")
        expect(repos[2].private):toBe("public")
        local branches = json.decode(Git.git_branches({ { name = "main", extra = 1 } }))
        expect(branches[1].name):toBe("main")
        expect(branches[1].extra):toBeNil()
    end)
end)

describe("format/registry", function()
    test("repos filtra pelo termo e formata contagem e tamanho", function()
        local rows = json.decode(Registry.repos({
            { name = "api", tag_count = 3, size_bytes = 5 * 1024 * 1024, created_at = "2026-03-01T10:00:00Z" },
            { name = "web" },
        }, "ap"))
        expect(#rows):toBe(1)
        expect(rows[1].name):toBe("api")
        expect(rows[1].tag_count):toBe("3")
        expect(rows[1].size):toBe("5 MB")
    end)

    test("repos sem termo traz todos, com defaults seguros", function()
        local rows = json.decode(Registry.repos({ { name = "web" } }, ""))
        expect(rows[1].tag_count):toBe("0")
        expect(rows[1].size):toBe("—")
        expect(rows[1].created):toBe("")
        expect(Registry.repos({ { name = "web" } }, "zzz")):toBe("[]")
    end)

    test("tags encurtam o digest para 12 e guardam o completo", function()
        local rows = json.decode(Registry.tags({
            { tag = "v1", digest = "sha256:0123456789abcdef", media_type = "m", size_bytes = 2048, updated_at = "2026-03-01T10:00:00Z" },
        }))
        expect(rows[1].digest):toBe("sha256:01234")
        expect(rows[1].digest_full):toBe("sha256:0123456789abcdef")
        expect(rows[1].size):toBe("2 KB")
    end)

    test("tokens que nunca foram usados dizem nunca", function()
        local rows = json.decode(Registry.tokens({
            { name = "ci", scope = "push", created_at = "2026-03-01T10:00:00Z" },
            { name = "dev", scope = "pull", created_at = "2026-03-01T10:00:00Z", last_used_at = "2026-03-02T10:00:00Z" },
        }))
        expect(rows[1].last_used):toBe("nunca")
        expect(rows[2].last_used):toMatch("^%d%d/%d%d %d%d:%d%d$")
    end)
end)

describe("format/docker_cleanup", function()
    test("resource_label traduz os conhecidos e devolve o resto", function()
        expect(DockerCleanup.resource_label("images")):toBe("imagens")
        expect(DockerCleanup.resource_label("build_cache")):toBe("cache de build")
        expect(DockerCleanup.resource_label("outro")):toBe("outro")
    end)

    test("sem execução anterior", function()
        expect(DockerCleanup.last_run_summary(nil)):toBe("ainda não rodou")
    end)

    test("resumo soma os bytes e lista cada recurso", function()
        local texto = DockerCleanup.last_run_summary({
            at = "2026-03-01T10:00:00Z",
            results = {
                { resource = "images", count = 3, reclaimed_bytes = 2 * 1024 * 1024 },
                { resource = "volumes", count = 1, reclaimed_bytes = 1024 * 1024 },
            },
        })
        expect(texto):toContain("3 MB liberados")
        expect(texto):toContain("(imagens 3, volumes 1)")
        expect(texto).never:toContain("falharam")
    end)

    test("um recurso com erro avisa e não soma contagem", function()
        local texto = DockerCleanup.last_run_summary({
            at = "2026-03-01T10:00:00Z",
            results = { { resource = "networks", error = "boom" } },
        })
        expect(texto):toContain("redes: erro")
        expect(texto):toContain("alguns recursos falharam, ver logs do daemon")
    end)

    test("execução sem resultados mostra só o cabeçalho", function()
        local texto = DockerCleanup.last_run_summary({ at = "2026-03-01T10:00:00Z", results = {} })
        expect(texto):toContain("liberados")
        expect(texto).never:toContain("(")
    end)
end)