test(modules): Check eval is identical to nixpkgs

We want these modules to be importable by people, as a set, even if they
don't want to use all of the options or import all of the dependencies

This test makes sure of 2 things
- Our modules can be evaluated on the default configuration with only
  stuff that is permitted in our README
- Without some configuration, our modules do not change the outputted
  system toplevel

This can be used as a rough proxy for being "safe to import", as
hopefully these modules won't introduce any changes to your config from
only an import

Change-Id: Ied99c3460a76f205de1724db2c531723cc3506b5
Reviewed-on: https://git.clicks.codes/c/Infra/NixFiles/+/812
Reviewed-by: Skyler Grey <minion@clicks.codes>
Tested-by: Skyler Grey <minion@clicks.codes>
diff --git a/modules.spec.nix b/modules.spec.nix
new file mode 100644
index 0000000..3360af9
--- /dev/null
+++ b/modules.spec.nix
@@ -0,0 +1,47 @@
+{ inputs, lib, channels, ... }:
+
+# Clicks modules must fulfil a contract consisting of 2 items:
+# - They must evaluate when running with only the NixOS modules and other Clicks
+#   modules
+# - They must not change config without being enabled
+#
+# We can pretty eloquently test for both of these by evaluating our modules with
+# just the nixpkgs modules, and then doing the same thing without our modules
+{
+  testEmptyEvalMayNotChangeOutput = let
+    neededForEval = [
+      {
+        system.stateVersion = "24.05";
+      }
+      {
+        fileSystems."/" = {
+          device = "none";
+          fsType = "tmpfs";
+        };
+      }
+      {
+        boot.loader.grub.device = "nodev";
+      }
+      {
+        nixpkgs.config = {
+          allowBroken = true;
+          allowUnfree = true;
+        };
+      }
+    ];
+    libPlusClicks = lib.attrsets.recursiveUpdate channels.unstable.lib {
+      clicks = lib.clicks; # Our own lib is permitted as a requirement... but we're doing this song/dance to stop us, say, relying on snowfall lib/agenix lix/whatever...
+    };
+  in {
+    expr = (inputs.unstable.lib.nixosSystem {
+      lib = libPlusClicks;
+      system = channels.unstable.system;
+      modules = (lib.attrsets.attrValues inputs.self.nixosModules) ++ neededForEval;
+    }).config.system.build.toplevel.outPath;
+    expected = (inputs.unstable.lib.nixosSystem {
+      lib = libPlusClicks;
+      system = channels.unstable.system;
+      modules = neededForEval;
+    }).config.system.build.toplevel.outPath;
+  };
+}