From 69e7f1d7255ee29431e36a2a750a79e948e6c174 Mon Sep 17 00:00:00 2001 From: Rex Lorenzo Date: Mon, 28 Jul 2025 17:38:58 -0700 Subject: [PATCH 1/3] Removing deprecated cfheader StatusText attribute * Attribute is no longer available in ColdFusion 2025. Using it causes an "Attribute validation error for CFHEADER tag". --- bonus/LogToScreen.cfc | 2 +- core/api.cfc | 10 +++++----- core/baseDeserializer.cfc | 2 +- dashboard/asset.cfm | 2 +- tests/tests/run.cfm | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bonus/LogToScreen.cfc b/bonus/LogToScreen.cfc index 615bfa17..68ffe744 100644 --- a/bonus/LogToScreen.cfc +++ b/bonus/LogToScreen.cfc @@ -9,7 +9,7 @@ - + diff --git a/core/api.cfc b/core/api.cfc index 70510359..658e1f10 100644 --- a/core/api.cfc +++ b/core/api.cfc @@ -146,7 +146,7 @@ - + @@ -177,7 +177,7 @@ - + An unhandled exception occurred: #root.message##root# -- #root.detail# @@ -422,7 +422,7 @@ - + @@ -498,7 +498,7 @@ - + @@ -1117,7 +1117,7 @@ - + diff --git a/core/baseDeserializer.cfc b/core/baseDeserializer.cfc index 6e1c8915..efddfc8f 100644 --- a/core/baseDeserializer.cfc +++ b/core/baseDeserializer.cfc @@ -40,7 +40,7 @@ - + diff --git a/dashboard/asset.cfm b/dashboard/asset.cfm index 02b916cc..85877243 100644 --- a/dashboard/asset.cfm +++ b/dashboard/asset.cfm @@ -28,7 +28,7 @@ - + diff --git a/tests/tests/run.cfm b/tests/tests/run.cfm index 3fcaecc6..632029fc 100644 --- a/tests/tests/run.cfm +++ b/tests/tests/run.cfm @@ -36,7 +36,7 @@ - + From c1d2e7a78a2b0403bda6d9fa3deb7bd8c103c0f0 Mon Sep 17 00:00:00 2001 From: Rex Lorenzo Date: Mon, 4 Aug 2025 11:08:54 -0700 Subject: [PATCH 2/3] Added compatibility layer for deprecated statusText attribute - Create cfHeaderUtils.cfc to detect CF version and conditionally use statusText - Add cfHeaderHelper.cfm with global setTaffyStatusHeader() function - Update all cfheader calls to use the new compatibility layer - Add comprehensive test coverage for version detection and header setting --- bonus/LogToScreen.cfc | 3 +- core/api.cfc | 28 +++- core/baseDeserializer.cfc | 17 ++- core/cfHeaderHelper.cfm | 21 +++ core/cfHeaderUtils.cfc | 63 ++++++++ dashboard/asset.cfm | 3 +- tests/tests/TestCfHeaderUtils.cfc | 192 ++++++++++++++++++++++++ tests/tests/TestHeaderCompatibility.cfc | 95 ++++++++++++ tests/tests/run.cfm | 3 +- 9 files changed, 414 insertions(+), 11 deletions(-) create mode 100644 core/cfHeaderHelper.cfm create mode 100644 core/cfHeaderUtils.cfc create mode 100644 tests/tests/TestCfHeaderUtils.cfc create mode 100644 tests/tests/TestHeaderCompatibility.cfc diff --git a/bonus/LogToScreen.cfc b/bonus/LogToScreen.cfc index 68ffe744..6088477a 100644 --- a/bonus/LogToScreen.cfc +++ b/bonus/LogToScreen.cfc @@ -9,7 +9,8 @@ - + + diff --git a/core/api.cfc b/core/api.cfc index 658e1f10..6ccde4e4 100644 --- a/core/api.cfc +++ b/core/api.cfc @@ -1,5 +1,23 @@ + + + + + + + + + + + + + + + + + + @@ -146,7 +164,7 @@ - + @@ -177,7 +195,7 @@ - + An unhandled exception occurred: #root.message##root# -- #root.detail# @@ -422,7 +440,7 @@ - + @@ -498,7 +516,7 @@ - + @@ -1117,7 +1135,7 @@ - + diff --git a/core/baseDeserializer.cfc b/core/baseDeserializer.cfc index efddfc8f..5a5effa2 100644 --- a/core/baseDeserializer.cfc +++ b/core/baseDeserializer.cfc @@ -34,13 +34,24 @@ + + + + + + + + + + + - + - + - + diff --git a/core/cfHeaderHelper.cfm b/core/cfHeaderHelper.cfm new file mode 100644 index 00000000..ec9a6d03 --- /dev/null +++ b/core/cfHeaderHelper.cfm @@ -0,0 +1,21 @@ + + + + + + + + // Use application scope to cache the utility instance + if (!structKeyExists(application, "_taffyHeaderUtils")) { + application._taffyHeaderUtils = createObject("component", "taffy.core.cfHeaderUtils").init(); + } + + application._taffyHeaderUtils.setStatusHeader(arguments.statusCode, arguments.statusText); + + \ No newline at end of file diff --git a/core/cfHeaderUtils.cfc b/core/cfHeaderUtils.cfc new file mode 100644 index 00000000..2fb667b6 --- /dev/null +++ b/core/cfHeaderUtils.cfc @@ -0,0 +1,63 @@ + + + + + + + + + + + + + if (structIsEmpty(arguments.serverInfo)) { + // Only reference server scope if no injection provided + variables.serverInfo = server; + } else { + variables.serverInfo = arguments.serverInfo; + } + + + + + + + + + + + if (isColdFusion2025OrLater()) { + cfheader(statuscode=arguments.statusCode); + } else { + if (len(arguments.statusText)) { + cfheader(statuscode=arguments.statusCode, statustext=arguments.statusText); + } else { + cfheader(statuscode=arguments.statusCode); + } + } + + + + + + // Cache the result since CF version won't change during execution + if (variables.isCF2025OrLater == "") { + var cfVersion = 0; + if (structKeyExists(variables.serverInfo, "coldfusion") && + structKeyExists(variables.serverInfo.coldfusion, "productname") && + variables.serverInfo.coldfusion.productname contains "ColdFusion") { + cfVersion = val(listFirst(variables.serverInfo.coldfusion.productversion)); + } + variables.isCF2025OrLater = (cfVersion >= 2025); + } + return variables.isCF2025OrLater; + + + + \ No newline at end of file diff --git a/dashboard/asset.cfm b/dashboard/asset.cfm index 85877243..90891eae 100644 --- a/dashboard/asset.cfm +++ b/dashboard/asset.cfm @@ -28,7 +28,8 @@ - + + diff --git a/tests/tests/TestCfHeaderUtils.cfc b/tests/tests/TestCfHeaderUtils.cfc new file mode 100644 index 00000000..edc58fb9 --- /dev/null +++ b/tests/tests/TestCfHeaderUtils.cfc @@ -0,0 +1,192 @@ +component extends="base" { + + function beforeTests() { + variables.headerUtils = ""; + } + + function setup() { + // Create fresh instance for each test + variables.headerUtils = createObject("component", "taffy.core.cfHeaderUtils"); + } + + // Test constructor with no server info (will use actual server scope) + function test_init_with_no_server_info() { + var result = variables.headerUtils.init(); + assertEquals(variables.headerUtils, result, "init() should return this"); + } + + // Test constructor with mock server info for CF 2024 + function test_init_with_cf2024_server_info() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "2024.0.0" + } + }; + + var result = variables.headerUtils.init(mockServerInfo); + assertEquals(variables.headerUtils, result, "init() should return this"); + assertEquals(false, result.isColdFusion2025OrLater(), "CF 2024 should not be detected as 2025+"); + } + + // Test constructor with mock server info for CF 2025 + function test_init_with_cf2025_server_info() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "2025.0.0" + } + }; + + var result = variables.headerUtils.init(mockServerInfo); + assertEquals(variables.headerUtils, result, "init() should return this"); + assertEquals(true, result.isColdFusion2025OrLater(), "CF 2025 should be detected as 2025+"); + } + + // Test constructor with mock server info for CF 2026 + function test_init_with_cf2026_server_info() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "2026.0.0" + } + }; + + var result = variables.headerUtils.init(mockServerInfo); + assertEquals(true, result.isColdFusion2025OrLater(), "CF 2026 should be detected as 2025+"); + } + + // Test with Lucee server info (should not be detected as CF 2025+) + function test_init_with_lucee_server_info() { + var mockServerInfo = { + lucee: { + version: "5.3.8.206" + }, + coldfusion: { + productname: "Lucee", + productversion: "5.3.8" + } + }; + + var result = variables.headerUtils.init(mockServerInfo); + assertEquals(false, result.isColdFusion2025OrLater(), "Lucee should not be detected as CF 2025+"); + } + + // Test with non-ColdFusion server info + function test_init_with_non_cf_server_info() { + var mockServerInfo = { + os: { + name: "Windows" + } + }; + + var result = variables.headerUtils.init(mockServerInfo); + assertEquals(false, result.isColdFusion2025OrLater(), "Non-CF server should not be detected as CF 2025+"); + } + + // Test version detection caching + function test_version_detection_caching() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "2025.0.0" + } + }; + + var util = variables.headerUtils.init(mockServerInfo); + + // First call + var result1 = util.isColdFusion2025OrLater(); + // Second call should use cached result + var result2 = util.isColdFusion2025OrLater(); + + assertEquals(result1, result2, "Cached version detection should return same result"); + assertEquals(true, result1, "CF 2025 should be detected correctly"); + } + + // Test setStatusHeader method (we can't easily test the actual cfheader call, + // but we can test that the method executes without error) + function test_setStatusHeader_with_cf2024() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "2024.0.0" + } + }; + + var util = variables.headerUtils.init(mockServerInfo); + + // This should execute without throwing an exception + // Note: We can't easily test the actual cfheader output in unit tests + try { + util.setStatusHeader(500, "Test Error"); + // If we get here, the method executed without error + assertTrue(true, "setStatusHeader should execute without error for CF 2024"); + } catch (any e) { + fail("setStatusHeader should not throw exception: " & e.message); + } + } + + function test_setStatusHeader_with_cf2025() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "2025.0.0" + } + }; + + var util = variables.headerUtils.init(mockServerInfo); + + try { + util.setStatusHeader(404, "Not Found"); + assertTrue(true, "setStatusHeader should execute without error for CF 2025"); + } catch (any e) { + fail("setStatusHeader should not throw exception: " & e.message); + } + } + + function test_setStatusHeader_without_statustext() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "2024.0.0" + } + }; + + var util = variables.headerUtils.init(mockServerInfo); + + try { + util.setStatusHeader(200); + assertTrue(true, "setStatusHeader should work without statusText parameter"); + } catch (any e) { + fail("setStatusHeader should not throw exception when statusText is empty: " & e.message); + } + } + + // Test edge case version strings + function test_version_detection_with_complex_version() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "2025.1.2.3-UPDATE1" + } + }; + + var util = variables.headerUtils.init(mockServerInfo); + assertEquals(true, util.isColdFusion2025OrLater(), "Should handle complex version strings"); + } + + function test_version_detection_with_cf11() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "11.0.0" + } + }; + + var util = variables.headerUtils.init(mockServerInfo); + assertEquals(false, util.isColdFusion2025OrLater(), "CF 11 should not be detected as 2025+"); + } + + +} \ No newline at end of file diff --git a/tests/tests/TestHeaderCompatibility.cfc b/tests/tests/TestHeaderCompatibility.cfc new file mode 100644 index 00000000..9c485205 --- /dev/null +++ b/tests/tests/TestHeaderCompatibility.cfc @@ -0,0 +1,95 @@ +component extends="base" { + + function beforeTests() { + // These tests will verify our changes work in real scenarios + } + + // Test that the global helper function works + function test_global_helper_function() { + try { + // Include the helper and test it doesn't throw errors + include "../../core/cfHeaderHelper.cfm"; + + // Create a simple test that exercises the function + // Note: We can't easily capture HTTP headers in unit tests, + // but we can verify the function executes without error + setTaffyStatusHeader(200, "OK"); + + assertTrue(true, "Global helper function should execute without error"); + } catch (any e) { + fail("Global helper function failed: " & e.message); + } + } + + // Test API component integration + function test_api_component_integration() { + try { + // Create an instance of the API component + var apiComponent = createObject("component", "taffy.core.api"); + + // Test that it has the setStatusHeader method + assertTrue(structKeyExists(apiComponent, "setStatusHeader"), "API component should have setStatusHeader method"); + + // Test calling the method (this will use private access, so we'll test indirectly) + assertTrue(true, "API component loads successfully"); + } catch (any e) { + fail("API component integration failed: " & e.message); + } + } + + // Test baseDeserializer component integration + function test_baseDeserializer_integration() { + try { + var deserializer = createObject("component", "taffy.core.baseDeserializer"); + + assertTrue(true, "Base deserializer loads successfully with header utils"); + } catch (any e) { + fail("Base deserializer integration failed: " & e.message); + } + } + + // Test LogToScreen bonus component + function test_logToScreen_integration() { + try { + var logger = createObject("component", "taffy.bonus.LogToScreen"); + var result = logger.init({}); + + assertTrue(true, "LogToScreen component loads successfully"); + } catch (any e) { + fail("LogToScreen integration failed: " & e.message); + } + } + + // Test version detection with actual server scope + function test_actual_server_version_detection() { + try { + var headerUtils = createObject("component", "taffy.core.cfHeaderUtils").init(); + var isModern = headerUtils.isColdFusion2025OrLater(); + + // This should work regardless of the actual CF version + assertTrue(isBoolean(isModern), "Version detection should return boolean"); + + // Log the actual detection for debugging + debug("Detected CF 2025+: " & isModern); + debug("Server product name: " & server.coldfusion.productname); + debug("Server version: " & server.coldfusion.productversion); + + } catch (any e) { + fail("Actual server version detection failed: " & e.message); + } + } + + // Test that our changes don't break existing functionality + function test_backwards_compatibility() { + try { + // Test that we can still create objects the old way + var headerUtils = createObject("component", "taffy.core.cfHeaderUtils"); + var initialized = headerUtils.init(); + + assertTrue(true, "Backwards compatibility maintained"); + } catch (any e) { + fail("Backwards compatibility test failed: " & e.message); + } + } + +} \ No newline at end of file diff --git a/tests/tests/run.cfm b/tests/tests/run.cfm index 632029fc..0ef5a69b 100644 --- a/tests/tests/run.cfm +++ b/tests/tests/run.cfm @@ -36,7 +36,8 @@ - + + From b34c218024d9129cff957c6309382c7759725cc3 Mon Sep 17 00:00:00 2001 From: Rex Lorenzo Date: Mon, 4 Aug 2025 11:17:16 -0700 Subject: [PATCH 3/3] Addressing copilot code review comments --- core/cfHeaderHelper.cfm | 9 ++++- core/cfHeaderUtils.cfc | 8 +++- tests/tests/TestCfHeaderUtils.cfc | 65 +++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/core/cfHeaderHelper.cfm b/core/cfHeaderHelper.cfm index ec9a6d03..64b4daf9 100644 --- a/core/cfHeaderHelper.cfm +++ b/core/cfHeaderHelper.cfm @@ -11,9 +11,14 @@ - // Use application scope to cache the utility instance + // Ensure thread-safe initialization using an exclusive application-scoped lock if (!structKeyExists(application, "_taffyHeaderUtils")) { - application._taffyHeaderUtils = createObject("component", "taffy.core.cfHeaderUtils").init(); + lock name="taffyHeaderUtilsInit" scope="application" type="exclusive" timeout="5" { + // Double-check inside the lock to prevent race conditions + if (!structKeyExists(application, "_taffyHeaderUtils")) { + application._taffyHeaderUtils = createObject("component", "taffy.core.cfHeaderUtils").init(); + } + } } application._taffyHeaderUtils.setStatusHeader(arguments.statusCode, arguments.statusText); diff --git a/core/cfHeaderUtils.cfc b/core/cfHeaderUtils.cfc index 2fb667b6..6de00e71 100644 --- a/core/cfHeaderUtils.cfc +++ b/core/cfHeaderUtils.cfc @@ -52,7 +52,13 @@ if (structKeyExists(variables.serverInfo, "coldfusion") && structKeyExists(variables.serverInfo.coldfusion, "productname") && variables.serverInfo.coldfusion.productname contains "ColdFusion") { - cfVersion = val(listFirst(variables.serverInfo.coldfusion.productversion)); + // Extract the first number from the productversion string, regardless of format + var versionMatch = reMatch("\d+", variables.serverInfo.coldfusion.productversion); + if (arrayLen(versionMatch) > 0) { + cfVersion = val(versionMatch[1]); + } else { + cfVersion = 0; + } } variables.isCF2025OrLater = (cfVersion >= 2025); } diff --git a/tests/tests/TestCfHeaderUtils.cfc b/tests/tests/TestCfHeaderUtils.cfc index edc58fb9..4dc12c10 100644 --- a/tests/tests/TestCfHeaderUtils.cfc +++ b/tests/tests/TestCfHeaderUtils.cfc @@ -176,6 +176,71 @@ component extends="base" { assertEquals(true, util.isColdFusion2025OrLater(), "Should handle complex version strings"); } + // Test version string with text prefix + function test_version_detection_with_text_prefix() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "ColdFusion 2025" + } + }; + + var util = variables.headerUtils.init(mockServerInfo); + assertEquals(true, util.isColdFusion2025OrLater(), "Should extract version from 'ColdFusion 2025' format"); + } + + // Test empty version string + function test_version_detection_with_empty_version() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "" + } + }; + + var util = variables.headerUtils.init(mockServerInfo); + assertEquals(false, util.isColdFusion2025OrLater(), "Empty version should return false"); + } + + // Test non-numeric version string + function test_version_detection_with_non_numeric_version() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "Latest" + } + }; + + var util = variables.headerUtils.init(mockServerInfo); + assertEquals(false, util.isColdFusion2025OrLater(), "Non-numeric version should return false"); + } + + // Test version with build prefix + function test_version_detection_with_build_prefix() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "Build 2025.0.0.12345" + } + }; + + var util = variables.headerUtils.init(mockServerInfo); + assertEquals(true, util.isColdFusion2025OrLater(), "Should extract version from 'Build 2025...' format"); + } + + // Test malformed version data + function test_version_detection_with_malformed_data() { + var mockServerInfo = { + coldfusion: { + productname: "ColdFusion", + productversion: "!@#$%" + } + }; + + var util = variables.headerUtils.init(mockServerInfo); + assertEquals(false, util.isColdFusion2025OrLater(), "Malformed version should return false"); + } + function test_version_detection_with_cf11() { var mockServerInfo = { coldfusion: {