test_that("freeze_prompt creates a frozen_prompt with correct hash", { fp <- freeze_prompt( system_prompt = "Classify sentiment.", user_template = "Text: {text}", model = "gpt-4o", temperature = 0, categories = c("positive", "negative", "neutral") ) expect_s3_class(fp, "frozen_prompt") expect_type(fp$hash, "character") expect_equal(nchar(fp$hash), 64) # SHA-256 = 64 hex chars expect_equal(fp$model, "gpt-4o") expect_equal(fp$temperature, 0) expect_equal(fp$categories, c("positive", "negative", "neutral")) }) test_that("freeze_prompt hash is deterministic", { fp1 <- freeze_prompt("Classify.", "Text: {text}", "gpt-4o", 0) fp2 <- freeze_prompt("Classify.", "Text: {text}", "gpt-4o", 0) expect_equal(fp1$hash, fp2$hash) }) test_that("freeze_prompt hash changes when spec changes", { fp1 <- freeze_prompt("Classify.", "Text: {text}", "gpt-4o", 0) fp2 <- freeze_prompt("Classify sentiment.", "Text: {text}", "gpt-4o", 0) expect_false(fp1$hash == fp2$hash) }) test_that("freeze_prompt category order does not affect hash", { fp1 <- freeze_prompt("Classify.", "{text}", "gpt-4o", 0, categories = c("a", "b", "c")) fp2 <- freeze_prompt("Classify.", "{text}", "gpt-4o", 0, categories = c("c", "a", "b")) expect_equal(fp1$hash, fp2$hash) }) test_that("verify_prompt returns TRUE for unmodified prompt", { fp <- freeze_prompt("Test.", "{text}", "gpt-4o", 0) expect_true(verify_prompt(fp)) }) test_that("verify_prompt returns FALSE for tampered prompt", { fp <- freeze_prompt("Test.", "{text}", "gpt-4o", 0) fp$system_prompt <- "Tampered!" expect_false(verify_prompt(fp)) }) test_that("print.frozen_prompt works", { fp <- freeze_prompt("Classify.", "{text}", "gpt-4o", 0, categories = c("pos", "neg")) expect_output(print(fp), "Frozen Prompt") expect_output(print(fp), "gpt-4o") }) test_that("export and reload round-trip preserves spec", { fp <- freeze_prompt( system_prompt = "Classify sentiment.", user_template = "Text: {text}", model = "gpt-4o", temperature = 0, categories = c("positive", "negative") ) tmp <- withr::local_tempfile(fileext = ".json") export_preregistration(fp, tmp) expect_true(file.exists(tmp)) reloaded <- load_frozen_prompt(tmp) expect_s3_class(reloaded, "frozen_prompt") expect_equal(reloaded$hash, fp$hash) expect_equal(reloaded$system_prompt, fp$system_prompt) expect_equal(reloaded$model, fp$model) expect_equal(reloaded$temperature, fp$temperature) expect_true(verify_prompt(reloaded)) }) test_that("export_preregistration refuses to overwrite by default", { fp <- freeze_prompt("Test.", "{text}", "gpt-4o", 0) tmp <- withr::local_tempfile(fileext = ".json") export_preregistration(fp, tmp) expect_error(export_preregistration(fp, tmp), "already exists") }) test_that("export_preregistration overwrites with flag", { fp <- freeze_prompt("Test.", "{text}", "gpt-4o", 0) tmp <- withr::local_tempfile(fileext = ".json") export_preregistration(fp, tmp) expect_no_error(export_preregistration(fp, tmp, overwrite = TRUE)) }) test_that("freeze_prompt validates inputs", { expect_error(freeze_prompt(123, "{text}", "gpt-4o")) expect_error(freeze_prompt("Classify.", 123, "gpt-4o")) })