diff --git a/src/Cellm.Tests/Unit/AddIn/ArgumentParserTests.cs b/src/Cellm.Tests/Unit/AddIn/ArgumentParserTests.cs index acae2fe..927ea75 100644 --- a/src/Cellm.Tests/Unit/AddIn/ArgumentParserTests.cs +++ b/src/Cellm.Tests/Unit/AddIn/ArgumentParserTests.cs @@ -1,4 +1,8 @@ +using System.Runtime.CompilerServices; using Cellm.AddIn; +using Cellm.AddIn.Exceptions; +using ExcelDna.Integration; +using Microsoft.Extensions.Configuration; using Xunit; using CellmRange = Cellm.AddIn.Range; @@ -6,6 +10,84 @@ namespace Cellm.Tests.Unit.AddIn; public class ArgumentParserTests { + private static ArgumentParser CreateParser() + { + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["CellmAddInConfiguration:DefaultProvider"] = "OpenAi", + ["CellmAddInConfiguration:DefaultTemperature"] = "0", + ["OpenAiConfiguration:DefaultModel"] = "gpt-4.1", + }) + .Build(); + + return new ArgumentParser(config); + } + + // ExcelReference constructor calls into Excel, so we create an uninitialized instance for unit tests + private static ExcelReference CreateExcelReference() => + (ExcelReference)RuntimeHelpers.GetUninitializedObject(typeof(ExcelReference)); + + #region Parse Old Syntax Tests + + [Fact] + public void Parse_OldArgumentOrder_CellsThenString_ThrowsCellmException() + { + var parser = CreateParser(); + + var ex = Assert.Throws(() => + parser + .AddInstructions(CreateExcelReference()) + .AddCells(new object[] { "Extract keywords", ExcelMissing.Value }) + .Parse()); + + Assert.Contains("argument order changed", ex.Message); + } + + [Fact] + public void Parse_OldArgumentOrder_CellsThenStringThenTemperature_ThrowsCellmException() + { + var parser = CreateParser(); + + var ex = Assert.Throws(() => + parser + .AddInstructions(CreateExcelReference()) + .AddCells(new object[] { "Extract keywords", 0.7 }) + .Parse()); + + Assert.Contains("argument order changed", ex.Message); + } + + [Fact] + public void Parse_OldArgumentOrder_CellsThenTemperature_ThrowsCellmException() + { + var parser = CreateParser(); + + var ex = Assert.Throws(() => + parser + .AddInstructions(CreateExcelReference()) + .AddCells(new object[] { 0.7, ExcelMissing.Value }) + .Parse()); + + Assert.Contains("argument order changed", ex.Message); + } + + [Fact] + public void Parse_OldTemperatureArgument_StringThenTemperature_ThrowsCellmException() + { + var parser = CreateParser(); + + var ex = Assert.Throws(() => + parser + .AddInstructions("Extract keywords") + .AddCells(new object[] { 0.7, ExcelMissing.Value }) + .Parse()); + + Assert.Contains("temperature argument was removed", ex.Message); + } + + #endregion + #region GetColumnName Tests [Theory] diff --git a/src/Cellm/AddIn/ArgumentParser.cs b/src/Cellm/AddIn/ArgumentParser.cs index d35dd4b..edc05bd 100644 --- a/src/Cellm/AddIn/ArgumentParser.cs +++ b/src/Cellm/AddIn/ArgumentParser.cs @@ -80,10 +80,16 @@ internal Arguments Parse() { // =PROMPT("Hello world") (string instructions, []) => new Arguments(provider, model, [], instructions, temperature, _outputShape), + // Old temperature argument: =PROMPT("instruction", 0.7), =PROMPT("instruction", A1:B2, 0.7), etc. + (string, object[] ranges) when ranges.Any(r => r is double) => + throw new CellmException("The temperature argument was removed in v0.5. Please use PROMPT(\"instruction\", A1:B2) and configure temperature via the ribbon UI."), // =PROMPT("Hello world", A1, B2, ...) (string instructions, object[] ranges) => new Arguments(provider, model, ParseRanges(ranges), instructions, temperature, _outputShape), // =PROMPT(A1:B2) (ExcelReference instructions, []) => new Arguments(provider, model, [], new Range(instructions.RowFirst, instructions.ColumnFirst, instructions.GetValue()), temperature, _outputShape), + // Old argument order: =PROMPT(A1:B2, "instruction"), =PROMPT(A1:B2, "instruction", 0.7), =PROMPT(A1:B2, 0.7), etc. + (ExcelReference, object[] ranges) when ranges.Any(r => r is string or double) => + throw new CellmException("The argument order changed in v0.5. Please use PROMPT(\"instruction\", A1:B2) instead of PROMPT(A1:B2, \"instruction\"). Temperature is now configured via the ribbon UI."), // =PROMPT(A1:B2, C1, D2, ...) (ExcelReference instructions, object[] ranges) => new Arguments(provider, model, ParseRanges(ranges), new Range(instructions.RowFirst, instructions.ColumnFirst, instructions.GetValue()), temperature, _outputShape), // Short-circuit if instructions contain an Excel error