{
  lib,
  config,
  extendModules,
  ...
}:
let
  inherit (lib)
    mkOption
    types
    ;

  indent = lib.replaceStrings [ "\n" ] [ "\n    " ];

  execTestCase = testCase: ''

    ### TEST ${testCase.name}: ${testCase.description} ###

    with subtest("${testCase.description}"):
        # Setup
        ${indent testCase.setupScript}

        # Test
        ${indent testCase.script}
  '';
in
{

  options = {
    setupScript = mkOption {
      type = types.lines;
      description = ''
        Python code that runs before the main test.

        Variables defined by this code will be available in the test.
      '';
      default = "";
    };
    testCases = mkOption {
      description = ''
        The test cases. See `testScript`.
      '';
      type = types.listOf (
        types.submodule {
          options.name = mkOption {
            type = types.str;
            description = ''
              The name of the test case.

              A repository with that name will be set up on the gitea server and locally.
            '';
          };
          options.description = mkOption {
            type = types.str;
            description = ''
              A description of the test case.
            '';
          };
          options.setupScript = mkOption {
            type = types.lines;
            description = ''
              Python code that runs before the test case.
            '';
            default = "";
          };
          options.script = mkOption {
            type = types.lines;
            description = ''
              Python code that runs the test.

              Variables defined by the global `setupScript`, as well as `testCases.*.setupScript` will be available here.
            '';
          };
        }
      );
    };
  };

  config = {
    nodes.client = {
      environment.variables = {
        _NIX_FORCE_HTTP = "1";
      };
      nix.settings.experimental-features = [
        "nix-command"
        "flakes"
      ];
    };
    setupScript = '''';
    testScript = ''
      start_all();

      ${config.setupScript}

      ### SETUP COMPLETE ###

      ${lib.concatStringsSep "\n" (map execTestCase config.testCases)}
    '';
  };
}
