/*
 * Copyright (C) 2017 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "snap.h"
#include "cleanup-funcs.h"

#include "snap.c"

#include <glib.h>

static void test_sc_security_tag_validate(void) {
    // First, test the names we know are good
    g_assert_true(sc_security_tag_validate("snap.name.app", "name", NULL));
    g_assert_true(sc_security_tag_validate("snap.network-manager.NetworkManager", "network-manager", NULL));
    g_assert_true(sc_security_tag_validate("snap.f00.bar-baz1", "f00", NULL));
    g_assert_true(sc_security_tag_validate("snap.foo.hook.bar", "foo", NULL));
    g_assert_true(sc_security_tag_validate("snap.foo.hook.bar-baz", "foo", NULL));
    g_assert_true(sc_security_tag_validate("snap.foo_instance.bar-baz", "foo_instance", NULL));
    g_assert_true(sc_security_tag_validate("snap.foo_instance.hook.bar-baz", "foo_instance", NULL));
    g_assert_true(sc_security_tag_validate("snap.foo_bar.hook.bar-baz", "foo_bar", NULL));

    // Now, test the names we know are bad
    g_assert_false(sc_security_tag_validate("pkg-foo.bar.0binary-bar+baz", "bar", NULL));
    g_assert_false(sc_security_tag_validate("pkg-foo_bar_1.1", NULL, NULL));
    g_assert_false(sc_security_tag_validate("appname/..", NULL, NULL));
    g_assert_false(sc_security_tag_validate("snap", NULL, NULL));
    g_assert_false(sc_security_tag_validate("snap.", NULL, NULL));
    g_assert_false(sc_security_tag_validate("snap.name", "name", NULL));
    g_assert_false(sc_security_tag_validate("snap.name.", "name", NULL));
    g_assert_false(sc_security_tag_validate("snap.name.app.", "name", NULL));
    g_assert_false(sc_security_tag_validate("snap.name.hook.", "name", NULL));
    g_assert_false(sc_security_tag_validate("snap!name.app", "!name", NULL));
    g_assert_false(sc_security_tag_validate("snap.-name.app", "-name", NULL));
    g_assert_false(sc_security_tag_validate("snap.name!app", "name!", NULL));
    g_assert_false(sc_security_tag_validate("snap.name.-app", "name", NULL));
    g_assert_false(sc_security_tag_validate("snap.name.app!hook.foo", "name", NULL));
    g_assert_false(sc_security_tag_validate("snap.name.app.hook!foo", "name", NULL));
    g_assert_false(sc_security_tag_validate("snap.name.app.hook.-foo", "name", NULL));
    g_assert_false(sc_security_tag_validate("snap.name.app.hook.f00", "name", NULL));
    g_assert_false(sc_security_tag_validate("sna.pname.app", "pname", NULL));
    g_assert_false(sc_security_tag_validate("snap.n@me.app", "n@me", NULL));
    g_assert_false(sc_security_tag_validate("SNAP.name.app", "name", NULL));
    g_assert_false(sc_security_tag_validate("snap.Name.app", "Name", NULL));
    // This used to be false but it's now allowed.
    g_assert_true(sc_security_tag_validate("snap.0name.app", "0name", NULL));
    g_assert_false(sc_security_tag_validate("snap.-name.app", "-name", NULL));
    g_assert_false(sc_security_tag_validate("snap.name.@app", "name", NULL));
    g_assert_false(sc_security_tag_validate(".name.app", "name", NULL));
    g_assert_false(sc_security_tag_validate("snap..name.app", ".name", NULL));
    g_assert_false(sc_security_tag_validate("snap.name..app", "name.", NULL));
    g_assert_false(sc_security_tag_validate("snap.name.app..", "name", NULL));
    // These contain invalid instance key
    g_assert_false(sc_security_tag_validate("snap.foo_.bar-baz", "foo", NULL));
    g_assert_false(sc_security_tag_validate("snap.foo_toolonginstance.bar-baz", "foo", NULL));
    g_assert_false(sc_security_tag_validate("snap.foo_inst@nace.bar-baz", "foo", NULL));
    g_assert_false(sc_security_tag_validate("snap.foo_in-stan-ce.bar-baz", "foo", NULL));
    g_assert_false(sc_security_tag_validate("snap.foo_in stan.bar-baz", "foo", NULL));

    // Test names that are both good, but snap name doesn't match security tag
    g_assert_false(sc_security_tag_validate("snap.foo.hook.bar", "fo", NULL));
    g_assert_false(sc_security_tag_validate("snap.foo.hook.bar", "fooo", NULL));
    g_assert_false(sc_security_tag_validate("snap.foo.hook.bar", "snap", NULL));
    g_assert_false(sc_security_tag_validate("snap.foo.hook.bar", "bar", NULL));
    g_assert_false(sc_security_tag_validate("snap.foo_instance.bar", "foo_bar", NULL));

    // Regression test 12to8
    g_assert_true(sc_security_tag_validate("snap.12to8.128to8", "12to8", NULL));
    g_assert_true(sc_security_tag_validate("snap.123test.123test", "123test", NULL));
    g_assert_true(sc_security_tag_validate("snap.123test.hook.configure", "123test", NULL));

    // regression test snap.eon-edg-shb-pulseaudio.hook.connect-plug-i2c
    g_assert_true(sc_security_tag_validate("snap.foo.hook.connect-plug-i2c", "foo", NULL));

    // make sure that component hooks can be validated
    g_assert_true(sc_security_tag_validate("snap.foo+comp.hook.install", "foo", "comp"));
    g_assert_true(sc_security_tag_validate("snap.foo_instance+comp.hook.install", "foo_instance", "comp"));
    // make sure that only hooks from components can be validated, not apps
    g_assert_false(sc_security_tag_validate("snap.foo+comp.app", "foo", "comp"));

    // unexpected component names should not work
    g_assert_false(sc_security_tag_validate("snap.foo+comp.hook.install", "foo", NULL));
    g_assert_false(sc_security_tag_validate("snap.foo+comp.hook.install", "foo", NULL));

    // missing component names when we expect one should not work
    g_assert_false(sc_security_tag_validate("snap.foo.hook.install", "foo", "comp"));
    g_assert_false(sc_security_tag_validate("snap.foo.hook.install", "foo", "comp"));

    // mismatch component names should not work
    g_assert_false(sc_security_tag_validate("snap.foo+comp.hook.install", "foo", "component"));

    // empty component name should not work
    g_assert_false(sc_security_tag_validate("snap.foo+comp.hook.install", "foo", ""));

    // invalid component names should not work
    g_assert_false(sc_security_tag_validate("snap.foo+coMp.hook.install", "foo", "coMp"));
    g_assert_false(sc_security_tag_validate("snap.foo+-omp.hook.install", "foo", "-omp"));

    // Security tag that's too long. The extra +2 is for the string
    // terminator and to allow us to make the tag too long to validate.
    char long_tag[SNAP_SECURITY_TAG_MAX_LEN + 2];
    memset(long_tag, 'b', sizeof long_tag);
    memcpy(long_tag, "snap.foo.b", sizeof "snap.foo.b" - 1);
    long_tag[sizeof long_tag - 1] = '\0';
    g_assert_true(strlen(long_tag) == SNAP_SECURITY_TAG_MAX_LEN + 1);
    g_assert_false(sc_security_tag_validate(long_tag, "foo", NULL));

    // If we make it one byte shorter it will be valid.
    long_tag[sizeof long_tag - 2] = '\0';
    g_assert_true(sc_security_tag_validate(long_tag, "foo", NULL));
}

static void test_sc_is_hook_security_tag(void) {
    // First, test the names we know are good
    g_assert_true(sc_is_hook_security_tag("snap.foo.hook.bar"));
    g_assert_true(sc_is_hook_security_tag("snap.foo.hook.bar-baz"));
    g_assert_true(sc_is_hook_security_tag("snap.foo_instance.hook.bar-baz"));
    g_assert_true(sc_is_hook_security_tag("snap.foo_bar.hook.bar-baz"));
    g_assert_true(sc_is_hook_security_tag("snap.foo_bar.hook.f00"));
    g_assert_true(sc_is_hook_security_tag("snap.foo_bar.hook.f-0-0"));

    // Now, test the names we know are not valid hook security tags
    g_assert_false(sc_is_hook_security_tag("snap.foo_instance.bar-baz"));
    g_assert_false(sc_is_hook_security_tag("snap.name.app!hook.foo"));
    g_assert_false(sc_is_hook_security_tag("snap.name.app.hook!foo"));
    g_assert_false(sc_is_hook_security_tag("snap.name.app.hook.-foo"));
    g_assert_false(sc_is_hook_security_tag("snap.foo_bar.hook.0abcd"));
    g_assert_false(sc_is_hook_security_tag("snap.foo.hook.abc--"));
    g_assert_false(sc_is_hook_security_tag("snap.foo_bar.hook.!foo"));
    g_assert_false(sc_is_hook_security_tag("snap.foo_bar.hook.-foo"));
    g_assert_false(sc_is_hook_security_tag("snap.foo_bar.hook!foo"));
    g_assert_false(sc_is_hook_security_tag("snap.foo_bar.!foo"));
}

static void test_sc_security_tag_to_unit_name(void) {
    {
        char *unit_name SC_CLEANUP(sc_cleanup_string) = sc_security_tag_to_unit_name("snap.foo+comp.hook.install");
        g_assert_cmpstr(unit_name, ==, "snap.foo\\x2bcomp.hook.install");
    }

    {
        char *unit_name SC_CLEANUP(sc_cleanup_string) = sc_security_tag_to_unit_name("snap.foo.bar");
        g_assert_cmpstr(unit_name, ==, "snap.foo.bar");
    }

    {
        char *unit_name SC_CLEANUP(sc_cleanup_string) = sc_security_tag_to_unit_name("snap.foo_dev.bar");
        g_assert_cmpstr(unit_name, ==, "snap.foo_dev.bar");
    }
}

static void test_sc_security_tag_to_unit_name_invalid(void) {
    if (g_test_subprocess()) {
        char *unit_name SC_CLEANUP(sc_cleanup_string) __attribute__((unused)) =
            sc_security_tag_to_unit_name("snap.foo|dev.bar");
        g_test_message("expected sc_security_tag_to_unit_name to not return");
        g_test_fail();
        return;
    }
    g_test_trap_subprocess(NULL, 0, 0);
    g_test_trap_assert_failed();
    g_test_trap_assert_stderr("unexpected character '|' in a validated security tag: 'snap.foo|dev.bar'\n");
}

static void test_sc_snap_or_instance_name_validate(gconstpointer data) {
    typedef void (*validate_func_t)(const char *, sc_error **);

    validate_func_t validate = (validate_func_t)data;
    bool is_instance = (validate == sc_instance_name_validate) ? true : false;

    sc_error *err = NULL;

    // Smoke test, a valid snap name
    validate("hello-world", &err);
    g_assert_null(err);

    // Smoke test: invalid character
    validate("hello world", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
    g_assert_cmpstr(sc_error_msg(err), ==, "snap name must use lower case letters, digits or dashes");
    sc_error_free(err);

    // Smoke test: no letters
    validate("", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
    g_assert_cmpstr(sc_error_msg(err), ==, "snap name must contain at least one letter");
    sc_error_free(err);

    // Smoke test: leading dash
    validate("-foo", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
    g_assert_cmpstr(sc_error_msg(err), ==, "snap name cannot start with a dash");
    sc_error_free(err);

    // Smoke test: trailing dash
    validate("foo-", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
    g_assert_cmpstr(sc_error_msg(err), ==, "snap name cannot end with a dash");
    sc_error_free(err);

    // Smoke test: double dash
    validate("f--oo", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
    g_assert_cmpstr(sc_error_msg(err), ==, "snap name cannot contain two consecutive dashes");
    sc_error_free(err);

    // Smoke test: NULL name is not valid
    validate(NULL, &err);
    g_assert_nonnull(err);
    // the only case when instance name validation diverges from snap name
    // validation
    if (!is_instance) {
        g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
        g_assert_cmpstr(sc_error_msg(err), ==, "snap name cannot be NULL");
    } else {
        g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME));
        g_assert_cmpstr(sc_error_msg(err), ==, "snap instance name cannot be NULL");
    }
    sc_error_free(err);

    const char *valid_names[] = {"aa",    "aaa", "aaaa", "a-a",  "aa-a",   "a-aa",
                                 "a-b-c", "a0",  "a-0",  "a-0a", "01game", "1-or-2"};
    for (size_t i = 0; i < SC_ARRAY_SIZE(valid_names); ++i) {
        g_test_message("checking valid snap name: %s", valid_names[i]);
        validate(valid_names[i], &err);
        g_assert_null(err);
    }
    const char *invalid_names[] = {
        // name cannot be empty
        "",
        // too short
        "a",
        // names cannot be too long
        "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "xxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx",
        "1111111111111111111111111111111111111111x",
        "x1111111111111111111111111111111111111111",
        "x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x",
        // dashes alone are not a name
        "-",
        "--",
        // double dashes in a name are not allowed
        "a--a",
        // name should not end with a dash
        "a-",
        // name cannot have any spaces in it
        "a ",
        " a",
        "a a",
        // a number alone is not a name
        "0",
        "123",
        "1-2-3",
        // identifier must be plain ASCII
        "日本語",
        "한글",
        "ру́сский язы́к",
    };
    for (size_t i = 0; i < SC_ARRAY_SIZE(invalid_names); ++i) {
        g_test_message("checking invalid snap name: >%s<", invalid_names[i]);
        validate(invalid_names[i], &err);
        g_assert_nonnull(err);
        g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
        sc_error_free(err);
    }
    // Regression test: 12to8 and 123test
    validate("12to8", &err);
    g_assert_null(err);
    validate("123test", &err);
    g_assert_null(err);

    // In case we switch to a regex, here's a test that could break things.
    const char good_bad_name[] = "u-94903713687486543234157734673284536758";
    char varname[sizeof good_bad_name] = {0};
    for (size_t i = 3; i <= sizeof varname - 1; i++) {
        g_assert_nonnull(memcpy(varname, good_bad_name, i));
        varname[i] = 0;
        g_test_message("checking valid snap name: >%s<", varname);
        validate(varname, &err);
        g_assert_null(err);
        sc_error_free(err);
    }
}

static void test_sc_snap_name_validate__respects_error_protocol(void) {
    if (g_test_subprocess()) {
        sc_snap_name_validate("hello world", NULL);
        g_test_message("expected sc_snap_name_validate to return");
        g_test_fail();
        return;
    }
    g_test_trap_subprocess(NULL, 0, 0);
    g_test_trap_assert_failed();
    g_test_trap_assert_stderr("snap name must use lower case letters, digits or dashes\n");
}

static void test_sc_instance_name_validate(void) {
    sc_error *err = NULL;

    sc_instance_name_validate("hello-world", &err);
    g_assert_null(err);
    sc_instance_name_validate("hello-world_foo", &err);
    g_assert_null(err);

    // just the separator
    sc_instance_name_validate("_", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
    g_assert_cmpstr(sc_error_msg(err), ==, "snap name must contain at least one letter");
    sc_error_free(err);

    // just name, with separator, missing instance key
    sc_instance_name_validate("hello-world_", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY));
    g_assert_cmpstr(sc_error_msg(err), ==, "instance key must contain at least one letter or digit");
    sc_error_free(err);

    // only separator and instance key, missing name
    sc_instance_name_validate("_bar", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
    g_assert_cmpstr(sc_error_msg(err), ==, "snap name must contain at least one letter");
    sc_error_free(err);

    sc_instance_name_validate("", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME));
    g_assert_cmpstr(sc_error_msg(err), ==, "snap name must contain at least one letter");
    sc_error_free(err);

    // third separator
    sc_instance_name_validate("foo_bar_baz", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME));
    g_assert_cmpstr(sc_error_msg(err), ==, "snap instance name can contain only one underscore");
    sc_error_free(err);

    // too long, 52
    sc_instance_name_validate("0123456789012345678901234567890123456789012345678901", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME));
    g_assert_cmpstr(sc_error_msg(err), ==, "snap instance name can be at most 51 characters long");
    sc_error_free(err);

    const char *valid_names[] = {
        "aa", "aaa", "aaaa", "aa_a", "aa_1", "aa_123", "aa_0123456789",
    };
    for (size_t i = 0; i < SC_ARRAY_SIZE(valid_names); ++i) {
        g_test_message("checking valid instance name: %s", valid_names[i]);
        sc_instance_name_validate(valid_names[i], &err);
        g_assert_null(err);
    }
    const char *invalid_names[] = {
        // too short
        "a",
        // only letters and digits in the instance key
        "a_--23))",
        "a_ ",
        "a_091234#",
        "a_123_456",
        // up to 10 characters for the instance key
        "a_01234567891",
        "a_0123456789123",
        // snap name must not be more than 40 characters, regardless of instance
        // key
        "01234567890123456789012345678901234567890_foobar",
        "01234567890123456789-01234567890123456789_foobar",
        // instance key  must be plain ASCII
        "foobar_日本語",
        // way too many underscores
        "foobar_baz_zed_daz",
        "foobar______",
    };
    for (size_t i = 0; i < SC_ARRAY_SIZE(invalid_names); ++i) {
        g_test_message("checking invalid instance name: >%s<", invalid_names[i]);
        sc_instance_name_validate(invalid_names[i], &err);
        g_assert_nonnull(err);
        sc_error_free(err);
    }
}

static void test_sc_snap_drop_instance_key_no_dest(void) {
    if (g_test_subprocess()) {
        sc_snap_drop_instance_key("foo_bar", NULL, 0);
        return;
    }
    g_test_trap_subprocess(NULL, 0, 0);
    g_test_trap_assert_failed();
}

static void test_sc_snap_drop_instance_key_short_dest(void) {
    if (g_test_subprocess()) {
        char dest[10] = {0};
        sc_snap_drop_instance_key("foo-foo-foo-foo-foo_bar", dest, sizeof dest);
        return;
    }
    g_test_trap_subprocess(NULL, 0, 0);
    g_test_trap_assert_failed();
}

static void test_sc_snap_drop_instance_key_short_dest2(void) {
    if (g_test_subprocess()) {
        char dest[3] = {0};  // "foo" sans the nil byte
        sc_snap_drop_instance_key("foo", dest, sizeof dest);
        return;
    }
    g_test_trap_subprocess(NULL, 0, 0);
    g_test_trap_assert_failed();
}

static void test_sc_snap_drop_instance_key_no_name(void) {
    if (g_test_subprocess()) {
        char dest[10] = {0};
        sc_snap_drop_instance_key(NULL, dest, sizeof dest);
        return;
    }
    g_test_trap_subprocess(NULL, 0, 0);
    g_test_trap_assert_failed();
}

static void test_sc_snap_drop_instance_key_short_dest_max(void) {
    if (g_test_subprocess()) {
        char dest[SNAP_NAME_LEN + 1] = {0};
        /* 40 chars (max valid length), pretend dest is the same length, no space for terminator */
        sc_snap_drop_instance_key("01234567890123456789012345678901234567890", dest, sizeof dest - 1);
        return;
    }
    g_test_trap_subprocess(NULL, 0, 0);
    g_test_trap_assert_failed();
}

static void test_sc_snap_drop_instance_key_basic(void) {
    char name[SNAP_NAME_LEN + 1] = {0xff};

    sc_snap_drop_instance_key("foo_bar", name, sizeof name);
    g_assert_cmpstr(name, ==, "foo");

    memset(name, 0xff, sizeof name);
    sc_snap_drop_instance_key("foo-bar_bar", name, sizeof name);
    g_assert_cmpstr(name, ==, "foo-bar");

    memset(name, 0xff, sizeof name);
    sc_snap_drop_instance_key("foo-bar", name, sizeof name);
    g_assert_cmpstr(name, ==, "foo-bar");

    memset(name, 0xff, sizeof name);
    sc_snap_drop_instance_key("_baz", name, sizeof name);
    g_assert_cmpstr(name, ==, "");

    memset(name, 0xff, sizeof name);
    sc_snap_drop_instance_key("foo", name, sizeof name);
    g_assert_cmpstr(name, ==, "foo");

    memset(name, 0xff, sizeof name);
    /* 40 chars - snap name length */
    sc_snap_drop_instance_key("0123456789012345678901234567890123456789", name, sizeof name);
    g_assert_cmpstr(name, ==, "0123456789012345678901234567890123456789");
}

static void test_sc_snap_split_instance_name_basic(void) {
    char name[SNAP_NAME_LEN + 1] = {0xff};
    char instance[20] = {0xff};

    sc_snap_split_instance_name("foo_bar", name, sizeof name, instance, sizeof instance);
    g_assert_cmpstr(name, ==, "foo");
    g_assert_cmpstr(instance, ==, "bar");
}

static void test_sc_snap_split_snap_component_basic(void) {
    char snap_name[SNAP_NAME_LEN + 1] = {0xff};
    char component_name[SNAP_NAME_LEN + 1] = {0xff};

    sc_snap_split_snap_component("foo+bar", snap_name, sizeof snap_name, component_name, sizeof component_name);
    g_assert_cmpstr(snap_name, ==, "foo");
    g_assert_cmpstr(component_name, ==, "bar");
}

static void test_sc_snap_component_validate(void) {
    sc_error *err = NULL;
    sc_snap_component_validate("snapname+compname", NULL, &err);
    g_assert_null(err);

    sc_snap_component_validate("snap-name+comp-name", NULL, &err);
    g_assert_null(err);

    // check that we fail if the snap name isn't in the snap component
    sc_snap_component_validate("snapname+compname", "othername", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_COMPONENT));
    sc_error_free(err);

    sc_snap_component_validate("snapname+compname", "othername_instance", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_COMPONENT));
    sc_error_free(err);

    // component name should never have an instance key in it, so this should
    // fail
    sc_snap_component_validate("snapname_instance+compname", "snapname_instance", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_COMPONENT));
    sc_error_free(err);

    sc_snap_component_validate("snapname_instance+compname", "snapname", &err);
    g_assert_nonnull(err);
    g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_COMPONENT));
    sc_error_free(err);

    // check that we can validate the snap name in the snap component
    sc_snap_component_validate("snapname+compname", "snapname", &err);
    g_assert_null(err);
    sc_snap_component_validate("snapname+compname", "snapname_instance", &err);
    g_assert_null(err);

    const char *cases[] = {
        NULL,
        "snap-name+",
        "+comp-name",
        "snap-name",
        "snap-name+comp_name",
        "loooooooooooooooooooooooooooong-snap-name+comp-name",
        "snap-name+loooooooooooooooooooooooooooong-comp-name",
    };

    for (size_t i = 0; i < SC_ARRAY_SIZE(cases); ++i) {
        g_test_message("checking invalid snap name: %s", cases[i]);
        sc_snap_component_validate(cases[i], NULL, &err);
        g_assert_nonnull(err);
        g_assert_true(sc_error_match(err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_COMPONENT));
        sc_error_free(err);
    }
}

static void test_sc_snap_component_validate_respects_error_protocol(void) {
    if (g_test_subprocess()) {
        sc_snap_component_validate("hello world+comp name", NULL, NULL);
        g_test_message("expected sc_snap_name_validate to return");
        g_test_fail();
        return;
    }
    g_test_trap_subprocess(NULL, 0, 0);
    g_test_trap_assert_failed();
    g_test_trap_assert_stderr("snap name in component must use lower case letters, digits or dashes\n");
}

static void __attribute__((constructor)) init(void) {
    g_test_add_func("/snap/sc_security_tag_validate", test_sc_security_tag_validate);
    g_test_add_func("/snap/sc_is_hook_security_tag", test_sc_is_hook_security_tag);
    g_test_add_func("/snap/sc_security_to_unit_name", test_sc_security_tag_to_unit_name);
    g_test_add_func("/snap/sc_security_to_unit_name_invalid", test_sc_security_tag_to_unit_name_invalid);

    g_test_add_data_func("/snap/sc_snap_name_validate", sc_snap_name_validate, test_sc_snap_or_instance_name_validate);
    g_test_add_func("/snap/sc_snap_name_validate/respects_error_protocol",
                    test_sc_snap_name_validate__respects_error_protocol);

    g_test_add_data_func("/snap/sc_instance_name_validate/just_name", sc_instance_name_validate,
                         test_sc_snap_or_instance_name_validate);
    g_test_add_func("/snap/sc_instance_name_validate/full", test_sc_instance_name_validate);

    g_test_add_func("/snap/sc_snap_drop_instance_key/basic", test_sc_snap_drop_instance_key_basic);
    g_test_add_func("/snap/sc_snap_drop_instance_key/no_dest", test_sc_snap_drop_instance_key_no_dest);
    g_test_add_func("/snap/sc_snap_drop_instance_key/no_name", test_sc_snap_drop_instance_key_no_name);
    g_test_add_func("/snap/sc_snap_drop_instance_key/short_dest", test_sc_snap_drop_instance_key_short_dest);
    g_test_add_func("/snap/sc_snap_drop_instance_key/short_dest2", test_sc_snap_drop_instance_key_short_dest2);
    g_test_add_func("/snap/sc_snap_drop_instance_key/short_dest_max", test_sc_snap_drop_instance_key_short_dest_max);

    g_test_add_func("/snap/sc_snap_split_instance_name/basic", test_sc_snap_split_instance_name_basic);
    g_test_add_func("/snap/sc_snap_split_snap_component/basic", test_sc_snap_split_snap_component_basic);
    g_test_add_func("/snap/sc_snap_component_validate", test_sc_snap_component_validate);
    g_test_add_func("/snap/sc_snap_component_validate/respects_error_protocol",
                    test_sc_snap_component_validate_respects_error_protocol);
}
