234 attempt to fix js error Uncaught SyntaxError: Unexpected identifier 'hidden'

This commit is contained in:
2026-04-27 17:57:24 +03:00
parent 8a32a0d5f0
commit 74c073bd7e
149 changed files with 14361 additions and 8886 deletions
+42 -76
View File
@@ -1,80 +1,46 @@
<cfcomponent extends="taffy.core.api" output="false">
<cfscript>
this.name = "Taffy_testSuite";
this.dirPath = getDirectoryFromPath(getCurrentTemplatePath());
this.mappings["/resources"] = this.dirPath & "resources/";
this.mappings["/mxunit"] = this.dirPath & "testbox/system/compat/";
this.mappings["/testbox"] = this.dirPath & "testbox/";
this.mappings["/bugLog"] = this.dirPath & "BugLogHQ/";
this.mappings["/Hoth"] = this.dirPath & "Hoth/";
this.mappings["/di1"] = this.dirPath & "di1/";
component {
this.system = createObject("java", "java.lang.System");
this.datasources["bugLog"] = {
class: (structKeyExists(server, "lucee")) ? 'org.gjt.mm.mysql.Driver' : 'com.mysql.jdbc.Driver',
connectionString: 'jdbc:mysql://localhost:3306/buglog?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT&useLegacyDatetimeCode=false',
url: 'jdbc:mysql://localhost:3306/buglog?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT&useLegacyDatetimeCode=false',
username: this.system.getenv("DB_USER"),
password: this.system.getenv("DB_PASS"),
driver: "other"
this.name = "TaffyTestHarness_" & hash(getCurrentTemplatePath());
this.sessionManagement = false;
// Set up mappings for tests
variables.testsPath = getDirectoryFromPath(getCurrentTemplatePath());
// Strip trailing slash before calling getDirectoryFromPath to reliably get parent dir across engines
variables.rootPath = getDirectoryFromPath(left(variables.testsPath, len(variables.testsPath) - 1));
this.mappings["/taffy"] = variables.rootPath;
this.mappings["/testbox"] = variables.testsPath & "testbox";
this.mappings["/tests"] = variables.testsPath;
this.mappings["/resources"] = variables.testsPath & "resources";
// Also set component paths for Lucee 6 compatibility
this.componentPaths = [
{
"physical": variables.rootPath,
"archive": "",
"primary": "physical",
"inspectTemplate": "always"
}
];
function onApplicationStart() {
// Initialize minimal Taffy context for tests that need it
application._taffy = {
settings: {
serializer: "core.nativeJsonSerializer",
noDataSends204NoContent: false
},
factory: new core.factory(),
compat: {
queryToArray: "missing",
queryToStruct: "missing"
}
};
return true;
}
function onRequestStart(targetPath) {
return true;
}
variables.framework = {};
variables.framework.disableDashboard = false;
variables.framework.reloadKey = "reload";
variables.framework.unhandledPaths = "/Taffy/tests/someFolder,/Taffy/tests/tests,/tests/someFolder,/tests/tests,BugLogHQ";
variables.framework.serializer = "nativeJsonSerializer";
variables.framework.useEtags = true;
variables.framework.globalHeaders = {};
variables.framework.globalHeaders["x-foo-globalheader"] = "snafu";
variables.framework.exposeHeaders = true;
variables.framework.environments = {};
variables.framework.environments.test = {};
variables.framework.environments.test.reloadPassword = 'dontpanic';
function getCacheKey(cfc, requestArguments, matchedURI) {
if(structKeyExists(requestArguments, "default")) {
return super.getCacheKey(argumentCollection = arguments);
}
return lCase(arguments.cfc & "_" & listSort(structKeyList(arguments.requestArguments), "textnocase"));
}
function getEnvironment(){
return "test";
}
function onRequest(string target) {
if (arguments.target contains "/BugLogHQ/") {
return true;
} else {
super.onRequest(arguments.target);
}
}
function onTaffyRequest(verb, cfc, requestArguments, mimeExt, headers, methodMetadata, matchedURI) {
var local = {};
//pass data into a resource by modifying requestArguments
if (structKeyExists(arguments.requestArguments, "hulk") and arguments.requestArguments.hulk eq "smash"){
arguments.requestArguments.dataFromOTR = "who let the hulk out?!";
}
//get basic auth data, if any, and pass it into the resources
local.credentials = getBasicAuthCredentials();
arguments.requestArguments.username = local.credentials.username;
arguments.requestArguments.password = local.credentials.password;
if (structKeyExists(arguments.requestArguments, "refuse") and arguments.requestArguments.refuse)
{
return noData().withStatus(405);
}
else
{
return true;
}
}
</cfscript>
</cfcomponent>
}
+55
View File
@@ -0,0 +1,55 @@
component {
/**
* Returns a sample query for testing queryToArray/queryToStruct
*/
function getSampleQuery() {
return queryNew(
"id,name,email,active",
"integer,varchar,varchar,bit",
[
{ id: 1, name: "John Doe", email: "john@example.com", active: true },
{ id: 2, name: "Jane Smith", email: "jane@example.com", active: true },
{ id: 3, name: "Bob Wilson", email: "bob@example.com", active: false }
]
);
}
/**
* Returns a single-row query for testing queryToStruct
*/
function getSingleRowQuery() {
return queryNew(
"id,name,email",
"integer,varchar,varchar",
[{ id: 1, name: "Test User", email: "test@example.com" }]
);
}
/**
* Returns sample struct data
*/
function getSampleStruct() {
return {
id: 123,
name: "Test Item",
tags: ["tag1", "tag2"],
metadata: {
created: now(),
version: "1.0"
}
};
}
/**
* Returns sample array data
*/
function getSampleArray() {
return [
{ id: 1, value: "first" },
{ id: 2, value: "second" },
{ id: 3, value: "third" }
];
}
}
+18
View File
@@ -0,0 +1,18 @@
component {
/**
* Walks the metadata chain to check if an object extends a given class.
* Works around Lucee's isInstanceOf() bug with mapped paths.
*/
public boolean function extendsClass(required any obj, required string className) {
if (!isObject(arguments.obj)) return false;
var meta = getMetadata(arguments.obj);
while (structKeyExists(meta, "name")) {
if (listLast(meta.name, ".") == listLast(arguments.className, ".")) return true;
if (!structKeyExists(meta, "extends")) break;
meta = meta.extends;
}
return false;
}
}
View File
-147
View File
@@ -1,147 +0,0 @@
/**
Aaron Greenlee
http://aarongreenlee.com/
This work is licensed under a Creative Commons Attribution-Share-Alike 3.0
Unported License.
// Original Info -----------------------------------------------------------
Author : Aaron Greenlee
Created : 01/12/2011
HothReporter offers a simple Web UI to report the errors observed by Hoth.
// Modifications :---------------------------------------------------------
*/
component
name='HothReporter'
accessors=false
output="false"
{
public Hoth.HothReporter function init (HothConfig)
{
// If a config object was not provided we
// will use our default.
variables.Config = (structKeyExists(arguments, 'HothConfig'))
? arguments.HothConfig
: new Hoth.config.HothConfig();
VARIABLES._NAME = 'Hoth_' & variables.Config.getApplicationName();
variables.exceptionKeys = ['detail','type','tagcontext','stacktrace','message'];// Required exception keys
variables.logPathIsRelative = variables.Config.getLogPathIsRelative();
variables.paths.LogPath = variables.Config.getLogPathExpanded(); // Get the root location for our logging.
variables.paths.Exceptions = variables.Config.getPath('exceptions'); // Track the unique exceptions.
variables.paths.Incidents = variables.Config.getPath('incidents'); // Track the hits per exception.
variables.paths.Report = variables.Config.getPath('exceptionReport'); // The actual report
variables.paths.Activity = variables.Config.getPath('exceptionReportActivity'); // Track when we save things. Helps understand volume.
//variables.paths.Index = variables.Config.getPath('exceptionIndex'); // Tracks the exception keys to prevent duplication
return this;
}
/** Quick report. Really a work in process.
* @exception Accepts a hash value or 'all'
**/
public struct function report (required string exception)
{
if (arguments.exception=='all')
{
return generateExceptionIndex();
} else {
local.filepath = variables.paths.Exceptions & '/' & arguments.exception;
local.exception = (fileExists(local.filepath))
? fileRead(local.filepath)
: serializeJSON ({'message'="That report no longer exists."});
return deserializeJSON (local.exception);
}
}
/** Quick report. Really a work in process.
* @exception Accepts a hash value or 'all'
**/
public array function delete (required string exception)
{
local.response = [];
if (arguments.exception == 'all')
{
lock name=VARIABLES._NAME timeout=variables.Config.getTimeToLock() type="exclusive" {
directoryDelete(variables.paths.Exceptions, true);
directoryDelete(variables.paths.Incidents, true);
directoryCreate(variables.paths.Exceptions);
directoryCreate(variables.paths.Incidents);
}
} else { local.exceptionPath = variables.paths.Exceptions & '/' & arguments.exception;
local.incidentPath = variables.paths.Incidents & '/' & arguments.exception;
local.response = [];
if (fileExists(local.exceptionPath))
{
fileDelete(local.exceptionPath);
arrayAppend(local.response, "Exception file deleted.");
} else {
arrayAppend(local.response, "Exception file did not exist!");
}
if (fileExists(local.incidentPath))
{
fileDelete(local.incidentPath);
arrayAppend(local.response, "Incident record file deleted.");
} else {
arrayAppend(local.response, "Incident record file not exist!");
}
return local.response;
}
}
/** Return the report's view **/
public string function getReportView () {
local.view = fileRead(expandPath('/Hoth') & '/views/report.html');
// Replace the Hoth URL
return replaceNoCase(
local.view
,'${HOTH_REPORT_URL}'
,variables.Config.getHothReportURL());
}
// -------------------------------------------------------------------------
private struct function generateExceptionIndex() {
// Read our file system
local.exceptions = directoryList (variables.paths.Exceptions,false);
local.incidents = directoryList (variables.paths.Exceptions,false);
local.report = {};
for (i=1;i LTE ArrayLen(local.exceptions);i=i+1) {
local.instance = {};
local.instance.filename =
listLast(local.exceptions[i],'\/');
if (left(local.instance.filename, 1) != '_')
{
//local.instance.exceptionDetail =
//fileRead (local.exceptions[i]);
if (!fileExists(variables.paths.Incidents & '/' & local.instance.filename))
{
local.instances = '';
} else {
local.instances =
fileRead(variables.paths.Incidents & '/' & local.instance.filename);
}
local.instance.incidentcount = listLen(local.instances,chr(10));
// Save our report
local.report[local.instance.filename] = local.instance;
}
}
return local.report;
}
}
-264
View File
@@ -1,264 +0,0 @@
/**
Aaron Greenlee
http://aarongreenlee.com/
This work is licensed under a Creative Commons Attribution-Share-Alike 3.0
Unported License.
// Original Info -----------------------------------------------------------
Author : Aaron Greenlee
Created : 10/01/2010
HothTracker is responsible for accepting a exception, tracking the frequency
of the exception and alerting developers for new, unique exceptions.
// Modifications :---------------------------------------------------------
Modified : 12/13/2010 9:52:41 AM by Aaron Greenlee.
- Now supporting ColdBox 3.0 RC1
*/
component
name='HothTracker'
accessors=false
{
public Hoth.HothTracker function init (HothConfig) {
// If a config object was not provided we
// will use our default.
variables.Config = (structKeyExists(arguments, 'HothConfig'))
? arguments.HothConfig
: new Hoth.object.HothConfig();
VARIABLES._NAME = 'Hoth_' & variables.Config.getApplicationName();
variables.exceptionKeys = ['detail','type','tagcontext','stacktrace','message']; // Required exception keys
variables.paths.LogPath = variables.Config.getLogPathExpanded(); // Get the root location for our logging.
variables.paths.Exceptions = variables.Config.getPath('exceptions'); // Track the unique exceptions.
variables.paths.Incidents = variables.Config.getPath('incidents'); // Track the hits per exception.
variables.paths.Report = variables.Config.getPath('exceptionReport'); // The actual report
variables.paths.Activity = variables.Config.getPath('exceptionReportActivity'); // Track when we save things. Helps understand volume.
//variables.paths.Index = variables.Config.getPath('exceptionIndex'); // Tracks the exception keys to prevent duplication
verifyDirectoryStructure();
try {
// For the life of this HothTracker, keep the ApplicationManager.
variables.HothApplicationManager = new Hoth.object.HothApplicationManager(HothConfig);
// Because the entire application db file is locked, we only want to
// learn about this application when the HothTracker is created. The
// actual amount of work done within the manager is small, but, it is
// does block all other processes for all other Hoth instances, so, we
// want to reduce calls to it to this constructor.
variables.HothApplicationManager.learnApplication(arguments.HothConfig);
} catch (any e) {
//writeDump(e);abort;
}
return this;
}
/** Track an exception.
@ExceptionStructure A ColdFusion cfcatch or a supported object from a Framework or Application. */
public boolean function track (any Exception) {
local.ExceptionStructure = parseException(arguments.Exception);
// If we did not parse what we are supposed to
// track, we will abort.
if (!local.ExceptionStructure.validException)
return false;
// ------------------------------------------------------------------------------
try {
// ------------------------------------------------------------------------------
local.e = {
detail = structKeyExists(local.ExceptionStructure,'detail') ? local.ExceptionStructure.detail : '_noDetail'
,message = structKeyExists(local.ExceptionStructure,'message') ? local.ExceptionStructure.message : '_noMessage'
,stack = structKeyExists(local.ExceptionStructure,'stacktrace') ? local.ExceptionStructure.stacktrace : '_no_stacktrace'
,context = structKeyExists(local.ExceptionStructure,'tagcontext') ? local.ExceptionStructure.tagcontext : '_no_tagcontext'
,format = structKeyExists(local.ExceptionStructure,'format') ? local.ExceptionStructure.format: '_no_format'
,url = CGI.HTTP_HOST & CGI.path_info
,client = CGI.HTTP_USER_AGENT
};
// Generate JSON for hashing
local.json = {};
local.k = '';
for(local.k in local.e)
local.json[k] = serializeJSON(local.e[local.k]);
// Hash a unique key for the content of each property within the exception
local.index = {};
local.index.stack = ( len(local.e.stack) > 0) ? hash(lcase(local.e.stack),'SHA') : '_no_stack';
local.index.key = lcase(local.index.stack);
local.saveDetails = false;
// Index the exception, count occurances and save details.
// Lock is unique to the exception.
lock name=local.index.key timeout=variables.Config.getTimeToLock() type="exclusive" {
local.filename = local.index.key & '.log';
local.exceptionFile = variables.paths.Exceptions & '/' & local.filename;
local.incidentsFile = variables.paths.Incidents & '/' & local.filename;
local.exceptionIsKnown = fileExists(local.exceptionFile);
if (!local.exceptionIsKnown)
fileWrite(local.exceptionFile ,serializeJSON(local.e),'UTF-8');
// Create an incident if the file does not exist
if (!fileExists(local.incidentsFile)) {
fileWrite(local.incidentsFile ,now() & '#chr(13)#','UTF-8');
} else {
local.file = fileOpen(local.incidentsFile,'append','utf-8');
fileWriteLine(local.file, now() & '|' & local.e.url );
fileClose(local.file);
}
}
// Outside the lock, send mail if requested
if (!local.exceptionIsKnown && variables.Config.getEmailNewExceptions() ) {
local.INetAddress = createObject( 'java', 'java.net.InetAddress' );
local.url = (len(CGI.QUERY_STRING) > 0)
? CGI.http_host & CGI.path_info & '?' & Cgi.QUERY_STRING
: CGI.http_host & CGI.path_info;
local.emailBody = [
"Hoth tracked a new exception (" & local.index.key & ")."
,"Message: " & local.e.message
,"Machine Name: " & local.INetAddress.getLocalHost().getHostName()
,"Address: " & local.url
];
local.Mail = new Mail( subject='Hoth Exception (' & variables.Config.getApplicationName() & ') ' & local.index.key
,to=variables.Config.getEmailNewExceptionsTo()
,from=variables.Config.getEmailNewExceptionsFrom());
// Attach the file
if ( variables.Config.getEmailNewExceptionsFile() ) {
local.Mail.addParam(file=local.exceptionFile);
ArrayAppend( local.emailBody, "To view the attached exception info, copy and paste into FireBug's console (x = exception) and press CRTL+Enter." );
}
// Show exception as HTML inline?
if ( variables.Config.getEmailExceptionsAsHTML() ) {
local.Mail.setType( "html" );
savecontent variable="local.emailBody" {
writeOutput( arrayToList( local.emailBody, "<br />" ) );
writeOutput( "<br />Exception Details:<br />");
writeDump( local.e );
}
local.Mail.setBody( local.emailBody );
}
else {
local.Mail.setBody( arrayToList( local.emailBody, "#chr(10)##chr(13)#" ) );
}
local.mail.Send();
}
// ------------------------------------------------------------------------------
} catch (any e) {
return false;
}
// ------------------------------------------------------------------------------
return true;
}
// Private Methods Follow -------------------------------------------------------
/** Parse an exception provided by a framework or supported application.
Hoth is easy to support. Just provide a Struct with at least the following keys:
'detail,type,tagContext,StackTrace,Message'
Or an object with the same information that Hoth can extract. */
private struct function parseException(any Exception) {
local.result = { validException = false };
// Return the meta data for our passed exception
local.md = getMetaData(Exception);
// Inspect the class tree to understand what type of object
// we are working with.
local.classTree = GetClassHeirarchy(local.md);
// Was our object built with ColdFusion? If so, it is custom.
// If not, the object is considered to be native and generated by CF.
local.exceptionType = (
listContainsNoCase(local.classTree, 'coldfusion.runtime.AttributeCollection', ' ')
) ? 'Custom' : 'Native';
if (local.exceptionType == 'Native')
{
local.result.detail = (structKeyExists(arguments.Exception, 'detail')) ? arguments.Exception.detail : 'undefined';
local.result.message = (structKeyExists(arguments.Exception, 'message')) ? arguments.Exception.message : 'undefined';
local.result.stacktrace = (structKeyExists(arguments.Exception, 'stacktrace')) ? arguments.Exception.stacktrace : 'undefined';
local.result.tagcontext = (structKeyExists(arguments.Exception, 'tagContext')) ? arguments.Exception.tagContext : 'undefined';
local.result.validException = true;
local.result.format = 'Native';
// ADDED by Benoit Hediard to get real detail and message in FW1
if (local.result.message == "Event handler exception." && structKeyExists(arguments.Exception, "Cause")) {
local.result.detail = (structKeyExists(arguments.Exception.Cause, 'detail')) ? arguments.Exception.Cause.detail : 'undefined';
local.result.message = (structKeyExists(arguments.Exception.Cause, 'message')) ? arguments.Exception.Cause.message : 'undefined';
};
return local.result;
} else {
//detail,type,tagcontext,stacktrace,message
switch(local.md.fullname) {
// ColdBox Excepiont
case 'coldbox.system.beans.ExceptionBean' :
case 'coldbox.system.web.context.ExceptionBean' :
local.result.detail = arguments.Exception.getDetail();
local.result.message = arguments.Exception.getMessage();
local.result.stacktrace = arguments.Exception.getStackTrace();
local.result.tagcontext = arguments.Exception.getTagContext();
local.result.validException = true;
local.result.format = 'ColdBox';
break;
// Unknown Exception Passed
default :
local.result.validException = false;
local.result.format = 'Unknown';
break;
/*case 'MachII.util.Exception' :
local.result = arguments.Exception.getCaughtException();
local.result.validException = true;
break;*/
}
}
return local.result;
}
/** Inspect the class heirarchy to return a string showing the class tree.
Original code is borrowed from Dominic Watson at the following address.
Rewritten for for CF9+/Railo features.
http://fusion.dominicwatson.co.uk/2007/09/coldfusion-objects-are-java-objects.html **/
private function GetClassHeirarchy(obj) {
local.thisClass = obj.GetClass();
local.sReturn = thisClass.GetName();
do{
local.thisClass = local.thisClass.GetSuperClass();
local.sReturn &= " " & thisClass.GetName();
} while(CompareNoCase(thisClass.GetName(), 'java.lang.Object'));
return local.sReturn;
}
/** Verify the desired directory structure exsits. */
private void function verifyDirectoryStructure() {
// Verify our index diectory exists
/* Ensure our directory structure is as expected. */
lock name=VARIABLES._NAME timeout=variables.Config.getTimeToLock() type="exclusive" {
if (!directoryExists(variables.paths.Exceptions)) {
directoryCreate(variables.paths.Exceptions);
fileWrite(variables.paths.Exceptions & '/_readme.txt','Hoth: The files within this directory contain the complete details for each unique exception.');
}
if (!directoryExists(variables.paths.Incidents)) {
directoryCreate(variables.paths.Incidents);
fileWrite(variables.paths.Incidents & '/_readme.txt','Hoth: The files within this directory contain the details about the volume of errors for each unique exception.');
}
}
return;
}
}
-41
View File
@@ -1,41 +0,0 @@
// This CFC offers no protection--thus, anyone can access this.
// You can implement this by renaming the CFC with a UUID--that can secure
// this through obscurity. But, I prefer the approach shown in the ColdBox
// example. That is what I use. I have not tried this code :(
component
{
/** Loads the Web UI (HTML) **/
remote function index () returnformat='plain' {
local.HothReport = new Hoth.HothReporter( new config.HothConfig() );
return local.HothReport.getReportView();
}
/** Access Hoth report data as JSON.
@exception If not provided a list of exceptions will be returned.
If provided, the value should be an exception hash which
modified the behavior to return information for only
that exception. **/
remote function report (string exception) returnformat='JSON' {
local.report = (structKeyExists(arguments, 'exception')
? arguments.exception
: 'all');
local.HothReport = new Hoth.HothReporter( new config.HothConfig() );
return local.HothReport.report(local.report);
}
/** Delete a report. **/
remote function delete (string exception)returnformat='JSON' {
if (!structKeyExists(arguments, 'exception'))
{
// We can delete all exceptions at once!
arguments.exception = 'all';
}
local.HothReport = new Hoth.HothReporter( new config.HothConfig() );
// Delete!
return local.HothReporter.delete(arguments.exception);
}
}
@@ -1,79 +0,0 @@
/**
Aaron Greenlee
http://aarongreenlee.com/
This work is licensed under a Creative Commons Attribution-Share-Alike 3.0
Unported License.
// Original Info -----------------------------------------------------------
Author : Aaron Greenlee
Created : 10/01/2010
Default configuration for Hoth.
*/
component
implements='Hoth.object.iHothConfig'
extends='Hoth.object.CoreConfig'
accessors=true {
// -------------------------------------------------------------------------
// BASIC HOTH SETTINGS (required)
// -------------------------------------------------------------------------
/** What is the name of your application? */
property name='applicationName' default='HothDefaultConfig';
/** How many seconds should we lock file operations?
For most operations this is exclusive to a unique exception. */
property name='timeToLock' default='1';
/** Where would you like Hoth to save exception data?
This folder should be empty when you start. */
property name='logPath' default='/Hoth/examples/Example_Logs';
/** Is the log file location relative to the webroot?
This folder should be empty when you start. */
property name='logPathIsRelative' default='true';
// -------------------------------------------------------------------------
// HOTH EMAIL SETTINGS (required)
// -------------------------------------------------------------------------
/** Would you like new exceptions to be emailed to you? */
property name='EmailNewExceptions' default='false';
/** What address(es) should receive these e-mails? */
property name='EmailNewExceptionsTo' default='you@yourdomain.com';
/** What address would you like these emails sent from? */
property name='EmailNewExceptionsFrom' default='hoth@yourdomain.com';
/** Would you like the raw JSON attached to the e-mail? */
property name='EmailNewExceptionsFile' default='false';
/** Would you like HTML emails which contain the exception? */
property name='EmailExceptionsAsHTML' default='false';
// -------------------------------------------------------------------------
// HOTH REPORT SETTINGS (required)
// -------------------------------------------------------------------------
/** How you access the Hoth reports is up to you. When the reports are
generated Hoth needs to know how to build links so you can navigate
the report and get information from the server.
You are responsible for deciding how you wish to access reports. Once
you have figured that out, please, write the full URL here.
**/
property
name='HothReportURL'
default='http://office/lib/Hoth/examples/ColdFusion/HothReportUI.cfc';
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
public function init()
{
super.init();
return this;
}
}
-1
View File
@@ -1 +0,0 @@
/*.hoth
-3
View File
@@ -1,3 +0,0 @@
This folder is populated by Hoth to keep track of all applications that
use this instance of Hoth so Hoth can find the log files for your
applications when generating reports.
@@ -1,98 +0,0 @@
/*
--------------------------------------------------------------------
I configure and load Hoth into the ColdBox cache
--------------------------------------------------------------------
Define settings in ColdBox.cfc for example:
interceptors = [
//Autowire
{
class="coldbox.system.interceptors.Autowire",
properties={}
},
//SES
{
class="coldbox.system.interceptors.SES",
properties={}
},
//HothTracker
{
class="hoth.extras.ColdBoxInterceptor",
properties={
EmailNewExceptions = true,
EmailNewExceptionsFile = true,
EmailNewExceptionsTo = "kaboom@mysite.com",
EmailNewExceptionsFrom = "server@mysite.com"
}
}
];
--------------------------------------------------------------------
*/
component
{
/* CONSTRUCTOR
------------------------------------------- */
/**
* This is the configuration method for the interceptor
*/
void function Configure()
{
// check for required settings
if ( !propertyExists( "EmailNewExceptionsTo" ) )
{
getPlugin( "logger" ).fatal( "hoth.extras.ColdBoxInterceptor.Configure", "The required 'EmailNewExceptionsTo' property has not been defined" );
throw(
type="hoth.extras.ColdBoxInterceptor"
, message="The required 'EmailNewExceptionsTo' property has not been defined"
);
}
// Use ColdBox settings if not specified
if ( !propertyExists( "applicationName" ) )
{
setProperty( "applicationName", getSetting( "AppName" ) & " (" & getSetting( "Environment" ) & ")" );
}
if ( !propertyExists( "logPath" ) )
{
setProperty( "logPath", getSetting( "ApplicationPath" ) & "logs/hoth" );
setProperty( "logPathIsRelative", false );
}
// Optional settings
if ( !propertyExists( "cacheKeyName" ) )
{
setProperty( "cacheKeyName", "hothtracker" );
}
}
/* INTERCEPTION POINTS
------------------------------------------- */
void function afterAspectsLoad( required any event, required struct interceptData )
{
var HothConfig = "";
var HothTracker = "";
var key = "";
var properties = getProperties();
HothConfig = new hoth.config.HothConfig();
for ( key in properties )
{
// use evaluate to dynamically called setters
if ( StructKeyExists( HothConfig, "set" & key ) )
{
evaluate( "HothConfig.set#key#( properties[ key ] )" );
}
}
HothTracker = new Hoth.HothTracker( HothConfig );
// store in ColdBox Cache
getColdboxOCM().set( getProperty( 'cacheKeyName' ), HothTracker, 0 );
getPlugin( "logger" ).info( "hoth.extras.ColdBoxInterceptor", "HothTracker configured and loaded in ColdBox cache using key '#getProperty( 'cacheKeyName' )#'" );
}
}
-4
View File
@@ -1,4 +0,0 @@
This is the default location for Hoth logs.
You should provide a better location in your Hoth Config object that
is unique to your application.
@@ -1,74 +0,0 @@
/**
Aaron Greenlee
http://aarongreenlee.com/
This work is licensed under a Creative Commons Attribution-Share-Alike 3.0
Unported License.
// Original Info -----------------------------------------------------------
Author : Aaron Greenlee
Created : 10/01/2010
Core configuration object for Hoth. This object provides all the
functionality for a Hoth Config Object. Individual implementations should
extend this object and provide their own values within the property
declaration.
// -------------------------------------------------------------------------
Modified : 12/13/2010 10:04:06 AM by Aaron Greenlee.
- Added support for global settings.
*/
component
accessors='true'
{
/** Construct a configuration object for Hoth. */
public function init ()
{
local.md = getMetadata(this);
local.n = arrayLen(local.md.properties);
for (local.i=1; local.i <= local.n; local.i++) {
local.fn = this['set' & local.md.properties[local.i].name];
local.fn(local.md.properties[local.i]['default']);
}
return this;
}
public void function setGlobalDatabasePath(required string path) {
variables.GlobalDatabasePath = arguments.path;
}
public string function getGlobalDatabasePath() {
return (structKeyExists(variables, 'GlobalDatabasePath'))
? variables.GlobalDatabasePath
: '/Hoth/db/';
}
/** Expands a path **/
public string function getLogPathExpanded ()
{
if (!structKeyExists(variables, 'logPathIsRelative')) {
variables.logPathIsRelative = false;
}
return variables.logPathIsRelative ? expandPath( getLogPath() ) : getLogPath();
}
/** Return a path for Hoth. */
public string function getPath (name)
{
switch (arguments.name) {
// Sub-Directories
case 'incidents' :
case 'exceptions' :
return getLogPathExpanded() & '/' & lcase(arguments.name);
break;
// Files
case 'exceptionIndex' :
case 'exceptionReport' :
case 'exceptionReportActivity' :
return getLogPathExpanded() & '/' & lcase(arguments.name) & '.log';
break;
}
}
}
@@ -1,118 +0,0 @@
/**
Aaron Greenlee
http://aarongreenlee.com/
This work is licensed under a Creative Commons Attribution-Share-Alike 3.0
Unported License.
// Original Info -----------------------------------------------------------
Author : Aaron Greenlee
Created : 12/13/2010
Responsible for helping Hoth report for multiple applications.
*/
component
name = 'HothApplicationManager'
accessors = true
{
property name='applications' type='array';
// The filename that holds application knowledge.
VARIABLES.APP_LOCK_KEY = 'HothApplicationDBFileLock';
/** Constructor.
* @HothConfig Requires a HothConfig Object.
*/
public Hoth.object.HothApplicationManager function init(
required HothConfig
){
setApplications( loadApplicationsFromDisk(HothConfig) );
return this;
}
/** To allow for easy reporting, Hoth needs to be able to track new
applications (or really, new log paths). When a new instance of
HothTracker is created, this method will be called. **/
public boolean function learnApplication(
required HothConfig
){
// Prevent any other threads from saving our new application
lock
name=VARIABLES.APP_LOCK_KEY
timeout=arguments.HothConfig.getTimeToLock()
type="exclusive"
{
local.knownApplications =
loadApplicationsFromDisk(argumentCollection=arguments);
for(local.index in local.knownApplications)
{
// Confirm our keys exist
if (structKeyExists(local.index, 'logPath'))
{
// If we find the same logPath, we know about this
// application and need to exit.
if (local.index.logPath == arguments.HothConfig.getLogPath())
{
return false;
}
}
}
// We did not find out application path within the file.
// Create the struct and append it to our array.
local.applicationDataToSave =
{
'applicationName' = arguments.HothConfig.getApplicationName()
,'logPath' = arguments.HothConfig.getLogPath()
,'created' = now()
};
arrayAppend(
local.knownApplications
,local.applicationDataToSave
);
// Save the contents of the entire array, replacing the entire file
local.path = arguments.HothConfig.getGlobalDatabasePath();
fileWrite(
expandPath(arguments.HothConfig.getGlobalDatabasePath() & 'applications.hoth')
,serializeJSON(local.knownApplications)
,'UTF-8'
);
}
return true;
}
/** Loads applications already observed by Hoth from disk.
* This process should be locked by the calling method.
* @HothConfig Requires a HothConfig Object. */
private array function loadApplicationsFromDisk (
required HothConfig
){
local.path = arguments.HothConfig.getGlobalDatabasePath();
local.appfile = expandPath(arguments.HothConfig.getGlobalDatabasePath() & 'applications.hoth');
// Return an empty array if the appFile does not exist.
if (!fileExists( local.appfile ) )
{
return [];
}
local.applicationsFound = fileRead( local.appfile );
// Delete the database if the file is invalid.
if (!isJSON(local.applicationsFound))
{
fileDelete( local.appfile );
return [];
}
// Convert the file to our desired structure
local.parsedDB = deserializeJSON(local.applicationsFound);
return local.parsedDB;
}
}
@@ -1,30 +0,0 @@
component
{
/**
* Returns the number of seconds since UTC January 1, 1970, 00:00:00
* (Epoch time).
*
* @param DateTime Date/time object you want converted to
* Epoch time.(Required)
*
* @return Returns a numeric value.
* @author Rob Brooks-Bilson (rbils@amkor.com)
* @version 1, June 21, 2002
*/
function GetEpochTimeFromLocal() {
local.datetime = 0;
if ( arrayLen(arguments) == 0)
{
local.datetime = Now();
} else {
local.datetime = arguments[1];
}
return
DateDiff(
"s"
,DateConvert("utc2Local", "January 1 1970 00:00")
,datetime);
}
}
@@ -1,19 +0,0 @@
/**
Aaron Greenlee
http://aarongreenlee.com/
This work is licensed under a Creative Commons Attribution-Share-Alike 3.0
Unported License.
// Original Info -----------------------------------------------------------
Author : Aaron Greenlee
Created : 10/01/2010
Interface for a Hoth Config Object.
*/
interface {
public function init ();
public string function getLogPathExpanded();
public string function getPath (name);
}
@@ -1,457 +0,0 @@
<cfcomponent displayname="bugLogListener">
<!---
bugLogListener.cfc
This is the main point of entry into the bugLog API. This component is the one that
actually processes the bug reports, it adds them to the database and is responsible
for processing any defined rules.
Created: 2007 - Oscar Arevalo - http://www.oscararevalo.com
--->
<cfset variables.startedOn = 0>
<cfset variables.oDAOFactory = 0>
<cfset variables.oRuleProcessor = 0>
<cfset variables.oConfig = 0>
<cfset variables.msgLog = arrayNew(1)>
<cfset variables.maxLogSize = 10>
<cfset variables.instanceName = "">
<cfset variables.autoCreateDefault = true>
<cffunction name="init" access="public" returntype="bugLogListener" hint="This is the constructor">
<cfargument name="config" required="true" type="config">
<cfargument name="instanceName" type="string" required="true">
<cfscript>
var cacheTTL = 300; // timeout in minutes for cache entries
variables.oConfig = arguments.config; // global configuration
variables.instanceName = arguments.instanceName;
logMessage("Starting BugLogListener (#instanceName#) service...");
// load settings
variables.maxLogSize = arguments.config.getSetting("service.maxLogSize");
// load DAO Factory
variables.oDAOFactory = createObject("component","bugLog.components.DAOFactory").init( variables.oConfig );
// load the finder objects
variables.oAppFinder = createObject("component","bugLog.components.appFinder").init( variables.oDAOFactory.getDAO("application") );
variables.oHostFinder = createObject("component","bugLog.components.hostFinder").init( variables.oDAOFactory.getDAO("host") );
variables.oSeverityFinder = createObject("component","bugLog.components.severityFinder").init( variables.oDAOFactory.getDAO("severity") );
variables.oSourceFinder = createObject("component","bugLog.components.sourceFinder").init( variables.oDAOFactory.getDAO("source") );
variables.oUserFinder = createObject("component","bugLog.components.userFinder").init( variables.oDAOFactory.getDAO("user") );
// load the rule processor
variables.oRuleProcessor = createObject("component","bugLog.components.ruleProcessor").init();
// create cache instances
variables.oAppCache = createObject("component","bugLog.components.lib.cache.cacheService").init(50, cacheTTL, false);
variables.oHostCache = createObject("component","bugLog.components.lib.cache.cacheService").init(50, cacheTTL, false);
variables.oSeverityCache = createObject("component","bugLog.components.lib.cache.cacheService").init(10, cacheTTL, false);
variables.oSourceCache = createObject("component","bugLog.components.lib.cache.cacheService").init(5, cacheTTL, false);
variables.oUserCache = createObject("component","bugLog.components.lib.cache.cacheService").init(50, cacheTTL, false);
// load scheduler
variables.scheduler = createObject("component","bugLog.components.schedulerService").init( variables.oConfig, variables.instanceName );
// load the mailer service
variables.mailerService = createObject("component","bugLog.components.MailerService").init( variables.oConfig );
// load rules
loadRules();
// configure history purging
configureHistoryPurging();
// configure the digest sender
configureDigest();
// record the date at which the service started
variables.startedOn = Now();
logMessage("BugLogListener Service (#instanceName#) Started");
</cfscript>
<cfreturn this>
</cffunction>
<cffunction name="logEntry" access="public" returntype="void" hint="This method adds a bug report entry into BugLog. Bug reports must be passed as RawEntryBeans">
<cfargument name="entryBean" type="rawEntryBean" required="true">
<cfscript>
var bean = arguments.entryBean;
var oEntry = 0;
var oApp = 0;
var oHost = 0;
var oSeverity = 0;
var oSource = 0;
var oDF = variables.oDAOFactory;
// get autocreate settings
var autoCreateApp = allowAutoCreate("application");
var autoCreateHost = allowAutoCreate("host");
var autoCreateSeverity = allowAutoCreate("severity");
var autoCreateSource = allowAutoCreate("source");
// extract related objects from bean
oApp = getApplicationFromBean( bean, autoCreateApp );
oHost = getHostFromBean( bean, autoCreateHost );
oSeverity = getSeverityFromBean( bean, autoCreateSeverity );
oSource = getSourceFromBean( bean, autoCreateSource );
// create entry
oEntry = createObject("component","bugLog.components.entry").init( oDF.getDAO("entry") );
oEntry.setDateTime(bean.getdateTime());
oEntry.setMessage(bean.getmessage());
oEntry.setApplicationID(oApp.getApplicationID());
oEntry.setSourceID(oSource.getSourceID());
oEntry.setSeverityID(oSeverity.getSeverityID());
oEntry.setHostID(oHost.getHostID());
oEntry.setExceptionMessage(bean.getexceptionMessage());
oEntry.setExceptionDetails(bean.getexceptionDetails());
oEntry.setCFID(bean.getcfid());
oEntry.setCFTOKEN(bean.getcftoken());
oEntry.setUserAgent(bean.getuserAgent());
oEntry.setTemplatePath(bean.gettemplate_Path());
oEntry.setHTMLReport(bean.getHTMLReport());
oEntry.setCreatedOn(bean.getReceivedOn());
// save entry
oEntry.save();
// process rules
variables.oRuleProcessor.processRules(bean, oEntry);
</cfscript>
</cffunction>
<cffunction name="getStartedOn" access="public" returntype="date" hint="Returns the date and time where this instance of BugLogListener was created">
<cfreturn variables.startedOn>
</cffunction>
<cffunction name="shutDown" access="public" returntype="void" hint="Performs any clean up action required">
<cfset logMessage("BugLogListener service stopped.")>
</cffunction>
<cffunction name="logMessage" access="public" output="false" returntype="void" hint="this method appends an entry to the messages log as well as displays the message on the console">
<cfargument name="msg" type="string" required="true" />
<cfset var System = CreateObject('java','java.lang.System') />
<cfset var txt = timeFormat(now(), 'HH:mm:ss') & ": " & msg>
<cfset System.out.println("BugLogListener: " & txt) />
<cflock name="bugLogListener_logMessage" type="exclusive" timeout="10">
<cfif arrayLen(variables.msgLog) gt variables.maxLogSize>
<cfset arrayDeleteAt(variables.msgLog, ArrayLen(variables.msgLog))>
</cfif>
<cfset arrayPrepend(variables.msgLog,txt)>
</cflock>
</cffunction>
<cffunction name="getConfig" returnType="any" access="public" hint="returns the config settings">
<cfreturn variables.oConfig>
</cffunction>
<cffunction name="getInstanceName" returnType="any" access="public" hint="returns the current instance name">
<cfreturn variables.instanceName>
</cffunction>
<cffunction name="validate" returntype="boolean" access="public" hint="Validates that a bug report is valid, if not throws an error. This only applies when the requireAPIKey setting is true, otherwise returns True always">
<cfargument name="entryBean" type="rawEntryBean" required="true">
<cfargument name="apiKey" type="string" required="true">
<cfscript>
// validate API
if(getConfig().getSetting("service.requireAPIKey",false)) {
if(arguments.apiKey eq "") {
throw(message="Invalid API Key",type="bugLog.invalidAPIKey");
}
var masterKey = getConfig().getSetting("service.APIKey");
if(arguments.apiKey neq masterKey) {
var user = getUserByAPIKey(arguments.apiKey);
if(!user.getIsAdmin() and arrayLen(user.getAllowedApplications())) {
// key is good, but since the user is a non-admin
// we need to validate the user can post bugs to the requested
// application.
var app = getApplicationFromBean( entryBean, false );
if(!user.isApplicationAllowed(app)) {
throw(message="Application not allowed",type="applicationNotAllowed");
}
}
}
}
// validate application
if(!allowAutoCreate("application")) {
getApplicationFromBean( entryBean, false );
}
// validate host
if(!allowAutoCreate("host")) {
getHostFromBean( entryBean, false );
}
// validte severity
if(!allowAutoCreate("severity")) {
getSeverityFromBean( entryBean, false );
}
// validate source
if(!allowAutoCreate("source")) {
getSourceFromBean( entryBean, false );
}
return true;
</cfscript>
</cffunction>
<cffunction name="reloadRules" access="public" returntype="void" hint="Reloads all rules">
<cfset loadRules()>
</cffunction>
<cffunction name="getMessageLog" access="public" returntype="array">
<cfreturn variables.msgLog>
</cffunction>
<!---- Private Methods ---->
<cffunction name="getApplicationFromBean" access="private" returntype="app" hint="Uses the information on the rawEntryBean to retrieve the corresponding Application object">
<cfargument name="entryBean" type="rawEntryBean" required="true">
<cfargument name="createIfNeeded" type="boolean" default="false">
<cfscript>
var key = "";
var bean = arguments.entryBean;
var oApp = 0;
var oDF = variables.oDAOFactory;
key = bean.getApplicationCode();
try {
// first we try to get it from the cache
oApp = variables.oAppCache.retrieve( key );
} catch(cacheService.itemNotFound e) {
// entry not in cache, so we get it from DB
try {
oApp = variables.oAppFinder.findByCode( key );
} catch(appFinderException.ApplicationCodeNotFound e) {
// code does not exist, so we need to create it (if autocreate enabled)
if(!arguments.createIfNeeded) throw(message="Invalid Application",type="invalidApplication");
oApp = createObject("component","bugLog.components.app").init( oDF.getDAO("application") );
oApp.setCode( key );
oApp.setName( key );
oApp.save();
}
// store entry in cache
variables.oAppCache.store( key, oApp );
}
</cfscript>
<cfreturn oApp>
</cffunction>
<cffunction name="getHostFromBean" access="private" returntype="host" hint="Uses the information on the rawEntryBean to retrieve the corresponding Host object">
<cfargument name="entryBean" type="rawEntryBean" required="true">
<cfargument name="createIfNeeded" type="boolean" default="false">
<cfscript>
var key = "";
var bean = arguments.entryBean;
var oHost = 0;
var oDF = variables.oDAOFactory;
key = bean.getHostName();
try {
// first we try to get it from the cache
oHost = variables.oHostCache.retrieve( key );
} catch(cacheService.itemNotFound e) {
// entry not in cache, so we get it from DB
try {
oHost = variables.oHostFinder.findByName( key );
} catch(hostFinderException.HostNameNotFound e) {
// code does not exist, so we need to create it (if autocreate enabled)
if(!arguments.createIfNeeded) throw(message="Invalid Host",type="invalidHost");
oHost = createObject("component","bugLog.components.host").init( oDF.getDAO("host") );
oHost.setHostName(key);
oHost.save();
}
// store entry in cache
variables.oHostCache.store( key, oHost );
}
</cfscript>
<cfreturn oHost>
</cffunction>
<cffunction name="getSeverityFromBean" access="private" returntype="severity" hint="Uses the information on the rawEntryBean to retrieve the corresponding Severity object">
<cfargument name="entryBean" type="rawEntryBean" required="true">
<cfargument name="createIfNeeded" type="boolean" default="false">
<cfscript>
var key = "";
var bean = arguments.entryBean;
var oSeverity = 0;
var oDF = variables.oDAOFactory;
key = bean.getSeverityCode();
try {
// first we try to get it from the cache
oSeverity = variables.oSeverityCache.retrieve( key );
} catch(cacheService.itemNotFound e) {
// entry not in cache, so we get it from DB
try {
oSeverity = variables.oSeverityFinder.findByCode( key );
} catch(severityFinderException.codeNotFound e) {
// code does not exist, so we need to create it (if autocreate enabled)
if(!arguments.createIfNeeded) throw(message="Invalid Severity",type="invalidSeverity");
oSeverity = createObject("component","bugLog.components.severity").init( oDF.getDAO("severity") );
oSeverity.setCode( key );
oSeverity.setName( key );
oSeverity.save();
}
// store entry in cache
variables.oSeverityCache.store( key, oSeverity );
}
</cfscript>
<cfreturn oSeverity>
</cffunction>
<cffunction name="getUserByAPIKey" access="private" returntype="user" hint="Finds a user object using by its API Key">
<cfargument name="apiKey" type="string" required="true">
<cfscript>
var oUser = 0;
try {
// first we try to get it from the cache
oUser = variables.oUserCache.retrieve( apiKey );
} catch(cacheService.itemNotFound e) {
// entry not in cache, so we get it from DB
try {
oUser = variables.oUserFinder.findByAPIKey( apiKey );
var qryUserApps = oDAOFactory.getDAO("userApplication").search(userID = oUser.getUserID());
var apps = oAppFinder.findByIDList(valueList(qryUserApps.applicationID));
oUser.setAllowedApplications(apps);
} catch(sourceFinderException.usernameNotFound e) {
// code does not exist, so we need to create it (if autocreate enabled)
throw(message="Invalid API Key",type="bugLog.invalidAPIKey");
}
// store entry in cache
variables.oUserCache.store( key, oUser );
}
return oUser;
</cfscript>
</cffunction>
<cffunction name="getSourceFromBean" access="private" returntype="source" hint="Uses the information on the rawEntryBean to retrieve the corresponding Source object">
<cfargument name="entryBean" type="rawEntryBean" required="true">
<cfargument name="createIfNeeded" type="boolean" default="false">
<cfscript>
var key = "";
var bean = arguments.entryBean;
var oSource = 0;
var oDF = variables.oDAOFactory;
key = bean.getSourceName();
try {
// first we try to get it from the cache
oSource = variables.oSourceCache.retrieve( key );
} catch(cacheService.itemNotFound e) {
// entry not in cache, so we get it from DB
try {
oSource = variables.oSourceFinder.findByName( key );
} catch(sourceFinderException.codeNotFound e) {
// code does not exist, so we need to create it (if autocreate enabled)
if(!arguments.createIfNeeded) throw(message="Invalid Source",type="invalidSource");
oSource = createObject("component","bugLog.components.source").init( oDF.getDAO("source") );
oSource.setName( key );
oSource.save();
}
// store entry in cache
variables.oSourceCache.store( key, oSource );
}
</cfscript>
<cfreturn oSource>
</cffunction>
<cffunction name="loadRules" access="private" returntype="void" hint="this method loads the rules into the rule processor">
<cfscript>
var oRule = 0;
var oExtensionsService = 0;
var aRules = arrayNew(1);
var i = 0;
var dao = 0;
var thisRule = 0;
// clear all existing rules
variables.oRuleProcessor.flushRules();
// get the rule definitions from the extensions service
dao = variables.oDAOFactory.getDAO("extension");
oExtensionsService = createObject("component","bugLog.components.extensionsService").init( dao );
aRules = oExtensionsService.getRules();
// create rule objects and load them into the rule processor
for(i=1; i lte arrayLen(aRules);i=i+1) {
thisRule = aRules[i];
if(thisRule.enabled) {
oRule =
thisRule.instance
.setListener(this)
.setDAOFactory( variables.oDAOFactory )
.setMailerService( variables.mailerService );
// add rule to processor
variables.oRuleProcessor.addRule(oRule);
}
}
</cfscript>
</cffunction>
<cffunction name="configureHistoryPurging" access="private" output="false" returntype="void">
<cfset var enabled = getConfig().getSetting("purging.enabled")>
<cfif enabled>
<cfset scheduler.setupTask("bugLogPurgeHistory",
"util/purgeHistory.cfm",
"03:00",
"daily") />
<cfelse>
<cfset scheduler.removeTask("bugLogPurgeHistory") />
</cfif>
</cffunction>
<cffunction name="configureDigest" access="private" output="false" returntype="void">
<cfset var config = getConfig()>
<cfset var enabled = config.getSetting("digest.enabled")>
<cfset var interval = config.getSetting("digest.schedulerIntervalHours")>
<cfset var startTime = config.getSetting("digest.schedulerStartTime")>
<cfif enabled>
<cfset scheduler.setupTask("bugLogSendDigest",
"util/sendDigest.cfm",
startTime,
interval*3600) />
<cfelse>
<cfset scheduler.removeTask("bugLogSendDigest") />
</cfif>
</cffunction>
<cffunction name="allowAutoCreate" returnType="boolean" access="private">
<cfargument name="entityType" type="string" required="true">
<cfreturn getConfig().getSetting("autocreate." & arguments.entityType, variables.autoCreateDefault)>
</cffunction>
</cfcomponent>
-874
View File
@@ -1,874 +0,0 @@
component {
variables._fw1_version = "3.5.0";
variables._di1_version = "1.1.1";
/*
Copyright (c) 2010-2015, Sean Corfield
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// CONSTRUCTOR
public any function init( any folders, struct config = { } ) {
variables.folderList = folders;
variables.folderArray = folders;
if ( isSimpleValue( folders ) ) {
variables.folderArray = listToArray( folders );
} else {
variables.folderList = arrayToList( folders );
}
var n = arrayLen( variables.folderArray );
for ( var i = 1; i <= n; ++i ) {
variables.folderArray[ i ] = trim( variables.folderArray[ i ] );
}
variables.config = config;
variables.beanInfo = { };
variables.beanCache = { };
variables.resolutionCache = { };
variables.initMethodCache = { };
variables.settersInfo = { };
variables.autoExclude = [
'/WEB-INF', '/Application.cfc', // never manage these!
'/Application.lc', '/Application.lucee',
// assume default name for intermediary:
'/MyApplication.cfc',
'/MyApplication.lc', '/MyApplication.lucee',
'framework.cfc', 'ioc.cfc', // legacy FW/1 / DI/1
// recent FW/1 + DI/1 + AOP/1 exclusions:
'/framework/aop.cfc', '/framework/beanProxy.cfc',
'/framework/ioc.cfc', '/framework/WireBoxAdapter.cfc',
'/framework/one.cfc',
// and Clojure-related exclusions:
'/framework/cfmljure.cfc', '/framework/cljcontroller.cfc',
'/framework/ioclj.cfc'
];
variables.listeners = 0;
setupFrameworkDefaults();
if ( structKeyExists( variables.config, 'loadListener' ) ) {
this.onLoad( variables.config.loadListener );
}
return this;
}
// PUBLIC METHODS
// programmatically register an alias
public any function addAlias( string aliasName, string beanName ) {
discoverBeans();
variables.beanInfo[ aliasName ] = variables.beanInfo[ beanName ];
return this;
}
// programmatically register new beans with the factory (add a singleton name/value pair)
public any function addBean( string beanName, any beanValue ) {
discoverBeans();
variables.beanInfo[ beanName ] = {
name = beanName, value = beanValue, isSingleton = true
};
return this;
}
// return true if the factory (or a parent factory) knows about the requested bean
public boolean function containsBean( string beanName ) {
discoverBeans();
return structKeyExists( variables.beanInfo, beanName ) ||
( structKeyExists( variables, 'parent' ) && variables.parent.containsBean( beanName ) );
}
// programmatically register new beans with the factory (add an actual CFC)
public any function declareBean( string beanName, string dottedPath, boolean isSingleton = true, struct overrides = { } ) {
discoverBeans();
var singleDir = '';
if ( listLen( dottedPath, '.' ) > 1 ) {
var cfc = listLast( dottedPath, '.' );
var dottedPart = left( dottedPath, len( dottedPath ) - len( cfc ) - 1 );
singleDir = singular( listLast( dottedPart, '.' ) );
}
var basePath = replace( dottedPath, '.', '/', 'all' );
var cfcPath = expandPath( '/' & basePath & '.cfc' );
var expPath = cfcPath;
if ( !fileExists( expPath ) ) expPath = expandPath( '/' & basePath & '.lc' );
if ( !fileExists( expPath ) ) expPath = expandPath( '/' & basePath & '.lucee' );
if ( !fileExists( expPath ) ) throw "Unable to find source file for #dottedPath#: expands to #cfcPath#";
var cfcPath = replace( expPath, chr(92), '/', 'all' );
var metadata = {
name = beanName, qualifier = singleDir, isSingleton = isSingleton,
path = cfcPath, cfc = dottedPath, metadata = cleanMetadata( dottedPath ),
overrides = overrides
};
variables.beanInfo[ beanName ] = metadata;
return this;
}
public any function factoryBean( string beanName, any factory, string methodName, array args = [ ], struct overrides = { } ) {
discoverBeans();
var metadata = {
name = beanName, isSingleton = false, // really?
factory = factory, method = methodName, args = args,
overrides = overrides
};
variables.beanInfo[ beanName ] = metadata;
return this;
}
// return the requested bean, fully populated
public any function getBean( string beanName ) {
discoverBeans();
if ( structKeyExists( variables.beanInfo, beanName ) ) {
return resolveBean( beanName );
} else if ( structKeyExists( variables, 'parent' ) ) {
return variables.parent.getBean( beanName );
} else {
throw 'bean not found: #beanName#';
}
}
// convenience API for metaprogramming perhaps?
public any function getBeanInfo( string beanName = '', boolean flatten = false,
string regex = '' ) {
discoverBeans();
if ( len( beanName ) ) {
// ask about a specific bean:
if ( structKeyExists( variables.beanInfo, beanName ) ) {
return variables.beanInfo[ beanName ];
}
if ( structKeyExists( variables, 'parent' ) ) {
return parentBeanInfo( beanName );
}
throw 'bean not found: #beanName#';
} else {
var result = { beanInfo = { } };
if ( structKeyExists( variables, 'parent' ) ) {
if ( flatten || len( regex ) ) {
structAppend( result.beanInfo, parentBeanInfoList( flatten ).beanInfo );
structAppend( result.beanInfo, variables.beanInfo );
} else {
result.beanInfo = variables.beanInfo;
result.parent = parentBeanInfoList( flatten );
};
} else {
result.beanInfo = variables.beanInfo;
}
if ( len( regex ) ) {
var matched = { };
for ( var name in result.beanInfo ) {
if ( REFind( regex, name ) ) {
matched[ name ] = result.beanInfo[ name ];
}
}
result.beanInfo = matched;
}
return result;
}
}
// return a copy of the DI/1 configuration
public struct function getConfig() {
// note: we only make a shallow copy
return structCopy( variables.config );
}
// return the DI/1 version
public string function getVersion() {
return variables.config.version;
}
// return true iff bean is known to be a singleton
public boolean function isSingleton( string beanName ) {
discoverBeans();
if ( structKeyExists( variables.beanInfo, beanName ) ) {
return variables.beanInfo[ beanName ].isSingleton;
} else if ( structKeyExists( variables, 'parent' ) ) {
try {
return variables.parent.isSingleton( beanName );
} catch ( any e ) {
return false; // parent doesn't know the bean therefore is it not singleton
}
} else {
return false; // we don't know the bean therefore it is not a managed singleton
}
}
// given a bean (by name, by type or by value), call the named
// setters with the specified property values
public any function injectProperties( any bean, struct properties ) {
if ( isSimpleValue( bean ) ) {
if ( containsBean( bean ) ) bean = getBean( bean );
else bean = construct( bean );
}
for ( var property in properties ) {
if ( !isNull( properties[ property ] ) ) {
var args = { };
args[ property ] = properties[ property ];
evaluate( 'bean.set#property#( argumentCollection = args )' );
}
}
return bean;
}
// empty the cache and reload all the singleton beans
// note: this does not reload the parent - if you have parent/child factories you
// are responsible for dealing with that logic (it's safe to reload a child but
// if you reload the parent, you must reload *all* child factories to ensure
// things stay consistent!)
public any function load() {
discoverBeans();
variables.beanCache = { };
variables.resolutionCache = { };
variables.initMethodCache = { };
for ( var key in variables.beanInfo ) {
if ( !structKeyExists( variables.beanInfo[ key ], "isSingleton" ) )
throw "internal error: bean #key# has no isSingleton flag!";
if ( variables.beanInfo[ key ].isSingleton ) {
getBean( key );
}
}
return this;
}
// add a listener for processing after a (re)load of the factory
// called with just the factory, should be a plain function
public any function onLoad( any listener ) {
var head = { next = variables.listeners, listener = listener };
variables.listeners = head;
return this;
}
// set the parent bean factory
public any function setParent( any parent ) {
variables.parent = parent;
return this;
}
// PRIVATE METHODS
private boolean function beanIsTransient( string singleDir, string dir, string beanName ) {
return singleDir == 'bean' ||
structKeyExists( variables.transients, dir ) ||
( structKeyExists( variables.config, "singletonPattern" ) &&
refindNoCase( variables.config.singletonPattern, beanName ) == 0 ) ||
( structKeyExists( variables.config, "transientPattern" ) &&
refindNoCase( variables.config.transientPattern, beanName ) > 0 );
}
private any function cachable( string beanName) {
var newObject = false;
var info = variables.beanInfo[ beanName ];
if ( info.isSingleton ) {
// cache on the qualified bean name:
var qualifiedName = beanName;
if ( structKeyExists( info, 'name' ) && structKeyExists( info, 'qualifier' ) ) {
qualifiedName = info.name & info.qualifier;
}
if ( !structKeyExists( variables.beanCache, qualifiedName ) ) {
variables.beanCache[ qualifiedName ] = construct( info.cfc );
newObject = true;
}
return { bean = variables.beanCache[ qualifiedName ], newObject = newObject };
} else {
return { bean = construct( info.cfc ), newObject = true };
}
}
private struct function cleanMetadata( string cfc ) {
var baseMetadata = metadata( cfc );
var iocMeta = { setters = { }, pruned = false, type = baseMetadata.type };
var md = { extends = baseMetadata };
do {
md = md.extends;
// gather up setters based on metadata:
var implicitSetters = false;
// we have implicit setters if: accessors="true" or persistent="true"
if ( structKeyExists( md, 'persistent' ) && isBoolean( md.persistent ) ) {
implicitSetters = md.persistent;
}
if ( structKeyExists( md, 'accessors' ) && isBoolean( md.accessors ) ) {
implicitSetters = implicitSetters || md.accessors;
}
if ( structKeyExists( md, 'properties' ) ) {
// due to a bug in ACF9.0.1, we cannot use var property in md.properties,
// instead we must use an explicit loop index... ugh!
var n = arrayLen( md.properties );
for ( var i = 1; i <= n; ++i ) {
var property = md.properties[ i ];
if ( implicitSetters &&
( !structKeyExists( property, 'setter' ) ||
isBoolean( property.setter ) && property.setter ) ) {
if ( structKeyExists( property, 'type' ) &&
property.type != 'any' ) {
iocMeta.setters[ property.name ] = 'typed';
} else {
iocMeta.setters[ property.name ] = 'implicit';
}
}
}
}
// still looking for a constructor?
if ( !structKeyExists( iocMeta, 'constructor' ) ) {
if ( structKeyExists( md, 'functions' ) ) {
// due to a bug in ACF9.0.1, we cannot use var property in md.functions,
// instead we must use an explicit loop index... ugh!
var n = arrayLen( md.functions );
for ( var i = 1; i <= n; ++i ) {
var func = md.functions[ i ];
if ( func.name == 'init' ) {
iocMeta.constructor = { };
if ( structKeyExists( func, 'parameters' ) ) {
// due to a bug in ACF9.0.1, we cannot use var arg in func.parameters,
// instead we must use an explicit loop index... ugh!
var m = arrayLen( func.parameters );
for ( var j = 1; j <= m; ++j ) {
var arg = func.parameters[ j ];
iocMeta.constructor[ arg.name ] = structKeyExists( arg, 'required' ) ? arg.required : false;
}
}
}
}
}
}
} while ( structKeyExists( md, 'extends' ) );
return iocMeta;
}
// in case an extension point wants to override actual construction:
private any function construct( string dottedPath ) {
return createObject( 'component', dottedPath );
}
// in case an extension point wants to override actual metadata retrieval:
private any function metadata( string dottedPath ) {
try {
return getComponentMetadata( dottedPath );
} catch ( any e ) {
var except = "Unable to getComponentMetadata(#dottedPath#) because: " &
e.message & ( len( e.detail ) ? " (#e.detail#)" : "" );
throw except;
}
}
private string function deduceDottedPath( string baseMapping, string basePath ) {
if ( right( basePath, 1 ) == '/' && len( basePath ) > 1 ) {
basePath = left( basePath, len( basePath ) - 1 );
}
var cfcPath = left( baseMapping, 1 ) == '/' ?
( len( baseMapping ) > 1 ? right( baseMapping, len( baseMapping ) - 1 ) : '' ) :
getFileFromPath( baseMapping );
if ( right( cfcPath, 1 ) == '/' && len( cfcPath ) > 1 ) {
cfcPath = left( cfcPath, len( cfcPath ) - 1 );
}
var expPath = basePath;
var notFound = true;
var dotted = '';
do {
var mapped = cfcPath;
if ( len( mapped ) && left( mapped, 1 ) != '.' ) mapped = '/' & mapped;
var mappedPath = replace( expandpath( mapped ), chr(92), '/', 'all' );
if ( mappedPath == basePath ) {
dotted = replace( cfcPath, '/', '.', 'all' );
notFound = false;
break;
}
var prevPath = expPath;
expPath = replace( getDirectoryFromPath( expPath ), chr(92), '/', 'all' );
if ( right( expPath, 1 ) == '/' && len( expPath ) > 1 ) {
expPath = left( expPath, len( expPath ) - 1 );
}
var progress = prevPath != expPath;
var piece = listLast( expPath, '/' );
cfcPath = piece & '/' & cfcPath;
} while ( progress );
if ( notFound ) {
throw 'unable to deduce dot-relative path for: #baseMapping# (#basePath#) root #expandPath("/")#';
}
return dotted;
}
private void function discoverBeans() {
if ( structKeyExists( variables, 'discoveryComplete' ) ) return;
lock name="#application.applicationName#_ioc1_#variables.folderList#" type="exclusive" timeout="30" {
if ( structKeyExists( variables, 'discoveryComplete' ) ) return;
variables.pathMapCache = { };
for ( var f in variables.folderArray ) {
discoverBeansInFolder( replace( f, chr(92), '/', 'all' ) );
}
variables.discoveryComplete = true;
}
onLoadEvent();
}
private void function discoverBeansInFolder( string mapping ) {
var folder = replace( expandPath( mapping ), chr(92), '/', 'all' );
var dotted = deduceDottedPath( mapping, folder );
var cfcs = [ ];
try {
cfcs = directoryList( folder, variables.config.recurse, 'path', '*.cfc' );
var lcs = directoryList( folder, variables.config.recurse, 'path', '*.lc' );
for ( var l in lcs ) arrayAppend( cfcs, l );
lcs = directoryList( folder, variables.config.recurse, 'path', '*.lucee' );
for ( l in lcs ) arrayAppend( cfcs, l );
} catch ( any e ) {
// assume bad path - ignore it, cfcs is empty list
}
for ( var cfcOSPath in cfcs ) {
var cfcPath = replace( cfcOSPath, chr(92), '/', 'all' );
// watch out for excluded paths:
var excludePath = false;
for ( var pattern in variables.config.exclude ) {
if ( findNoCase( pattern, cfcPath ) ) {
excludePath = true;
break;
}
}
if ( excludePath ) continue;
var relPath = right( cfcPath, len( cfcPath ) - len( folder ) );
var extN = 1 + len( listLast( cfcPath, "." ) );
relPath = left( relPath, len( relPath ) - extN );
var dir = listLast( getDirectoryFromPath( cfcPath ), '/' );
var singleDir = singular( dir );
var beanName = listLast( relPath, '/' );
var dottedPath = dotted & replace( relPath, '/', '.', 'all' );
try {
var metadata = {
name = beanName, qualifier = singleDir, isSingleton = !beanIsTransient( singleDir, dir, beanName ),
path = cfcPath, cfc = dottedPath, metadata = cleanMetadata( dottedPath )
};
if ( structKeyExists( metadata.metadata, "type" ) && metadata.metadata.type == "interface" ) {
continue;
}
if ( structKeyExists( variables.beanInfo, beanName ) ) {
if ( variables.config.omitDirectoryAliases ) {
throw '#beanName# is not unique (and omitDirectoryAliases is true)';
}
structDelete( variables.beanInfo, beanName );
variables.beanInfo[ beanName & singleDir ] = metadata;
} else {
variables.beanInfo[ beanName ] = metadata;
if ( !variables.config.omitDirectoryAliases ) {
variables.beanInfo[ beanName & singleDir ] = metadata;
}
}
} catch ( any e ) {
// wrap the exception so we can add bean name for debugging
// this trades off any stack trace information for the bean name but
// since we are only trying to get metadata, the latter should be
// more useful than the former
var except = "Problem with metadata for #beanName# (#dottedPath#) because: " &
e.message & ( len( e.detail ) ? " (#e.detail#)" : "" );
}
}
}
private struct function findSetters( any cfc, struct iocMeta ) {
var liveMeta = { setters = iocMeta.setters };
if ( !iocMeta.pruned ) {
// need to prune known setters of transients:
var prunable = { };
for ( var known in iocMeta.setters ) {
if ( !isSingleton( known ) ) {
prunable[ known ] = true;
}
}
for ( known in prunable ) {
structDelete( iocMeta.setters, known );
}
iocMeta.pruned = true;
}
// gather up explicit setters:
for ( var member in cfc ) {
var method = cfc[ member ];
var n = len( member );
if ( isCustomFunction( method ) && left( member, 3 ) == 'set' && n > 3 ) {
var property = right( member, n - 3 );
if ( !isSingleton( property ) ) {
// ignore properties that we know to be transients...
continue;
}
if ( !structKeyExists( liveMeta.setters, property ) ) {
liveMeta.setters[ property ] = 'explicit';
}
}
}
return liveMeta;
}
private any function forceCache( any bean, string beanName) {
var info = variables.beanInfo[ beanName ];
if ( info.isSingleton ) {
// cache on the qualified bean name:
var qualifiedName = beanName;
if ( structKeyExists( info, 'name' ) && structKeyExists( info, 'qualifier' ) ) {
qualifiedName = info.name & info.qualifier;
}
variables.beanCache[ qualifiedName ] = bean;
}
}
private boolean function isConstant ( string beanName ) {
return structKeyExists( variables.beanInfo, beanName ) &&
structKeyExists( variables.beanInfo[ beanName ], 'value' );
}
private void function logMissingBean( string beanName, string resolvingBeanName = '' ) {
var sys = createObject( 'java', 'java.lang.System' );
if ( len( resolvingBeanName ) ) {
sys.out.println( 'bean not found: #beanName#; while resolving #resolvingBeanName#' );
} else {
sys.out.println( 'bean not found: #beanName#' );
}
}
private void function missingBean( string beanName, string resolvingBeanName = '' ) {
if ( variables.config.strict ) {
if ( len( resolvingBeanName ) ) {
throw 'bean not found: #beanName#; while resolving #resolvingBeanName#';
} else {
throw 'bean not found: #beanName#';
}
} else {
logMissingBean( beanName, resolvingBeanName );
}
}
private void function onLoadEvent() {
var head = variables.listeners;
while ( isStruct( head ) ) {
if ( isCustomFunction( head.listener ) ||
( listFirst( server.coldfusion.productVersion ) >= 10 &&
isClosure( head.listener ) ) ) {
head.listener( this );
} else if ( isObject( head.listener ) ) {
head.listener.onLoad( this );
} else if ( isSimpleValue( head.listener ) &&
containsBean( head.listener ) ) {
getBean( head.listener ).onLoad( this );
} else {
throw "invalid onLoad listener registered: #head.listener.toString()#";
}
head = head.next;
}
}
private any function parentBeanInfo( string beanName ) {
// intended to be adaptable to whatever the parent is:
if ( structKeyExists( variables.parent, 'getBeanInfo' ) ) {
// smells like DI/1 or compatible:
return variables.parent.getBeanInfo( beanName );
}
if ( structKeyExists( variables.parent, 'getBeanDefinition' ) ) {
// smells like ColdSpring or compatible:
return variables.parent.getBeanDefinition( beanName );
}
// unknown:
return { };
}
private any function parentBeanInfoList( boolean flatten ) {
// intended to be adaptable to whatever the parent is:
if ( structKeyExists( variables.parent, 'getBeanInfo' ) ) {
// smells like DI/1 or compatible:
return variables.parent.getBeanInfo( flatten = flatten );
}
if ( structKeyExists( variables.parent, 'getBeanDefinitionList' ) ) {
// smells like ColdSpring or compatible:
return variables.parent.getBeanDefinitionList();
}
// unknown
return { };
}
private any function resolveBean( string beanName ) {
// do enough resolution to create and initialization this bean
// returns a struct of the bean and a struct of beans and setters still to run
// construction phase:
var partialBean = resolveBeanCreate( beanName, { injection = { }, dependencies = { } } );
if ( structKeyExists( variables.resolutionCache, beanName ) &&
variables.resolutionCache[ beanName ] ) {
// fully resolved, no action needed this time
} else {
var checkForPostInjection = structKeyExists( variables.config, 'initMethod' );
var initMethod = checkForPostInjection ? variables.config.initMethod : '';
var postInjectables = { };
// injection phase:
// now perform all of the injection:
for ( var name in partialBean.injection ) {
var injection = partialBean.injection[ name ];
if ( checkForPostInjection && !isConstant( name ) && structKeyExists( injection.bean, initMethod ) ) {
postInjectables[ name ] = true;
}
for ( var property in injection.setters ) {
if ( injection.setters[ property ] == 'typed' &&
variables.config.omitTypedProperties ) {
// we do not inject typed properties!
continue;
}
var args = { };
if ( structKeyExists( injection.overrides, property ) ) {
args[ property ] = injection.overrides[ property ];
} else if ( structKeyExists( partialBean.injection, property ) ) {
args[ property ] = partialBean.injection[ property ].bean;
} else if ( structKeyExists( variables, 'parent' ) && variables.parent.containsBean( property ) ) {
args[ property ] = variables.parent.getBean( property );
} else {
missingBean( property, beanName );
continue;
}
evaluate( 'injection.bean.set#property#( argumentCollection = args )' );
}
}
// post-injection, pre-init-method phase:
for ( name in partialBean.injection ) {
injection = partialBean.injection[ name ];
setupInitMethod( name, injection.bean );
}
// see if anything needs post-injection, init-method calls:
for ( var postName in postInjectables ) {
callInitMethod( postName, postInjectables, partialBean, initMethod );
}
variables.resolutionCache[ beanName ] = isSingleton( beanName );
}
return partialBean.bean;
}
private void function callInitMethod( string name, struct injectables, struct info, string method ) {
if ( injectables[ name ] ) {
injectables[ name ] = false; // this ensures we don't try to init the same
// bean twice - and also breaks circular dependencies...
if ( structKeyExists( info.dependencies, name ) ) {
for ( var depName in info.dependencies[ name ] ) {
if ( structKeyExists( injectables, depName ) &&
injectables[ depName ] ) {
callInitMethod( depName, injectables, info, method );
}
}
}
if ( structKeyExists( variables.initMethodCache, name ) &&
variables.initMethodCache[ name ] ) {
} else {
variables.initMethodCache[ name ] = isSingleton( name );
var bean = info.injection[ name ].bean;
evaluate( 'bean.#method#()' );
}
}
}
private struct function resolveBeanCreate( string beanName, struct accumulator ) {
var bean = 0;
if ( structKeyExists( variables.beanInfo, beanName ) ) {
var info = variables.beanInfo[ beanName ];
accumulator.dependencies[ beanName ] = { };
if ( structKeyExists( info, 'cfc' ) ) {
/*******************************************************/
var metaBean = cachable( beanName );
var overrides = structKeyExists( info, 'overrides' ) ? info.overrides : { };
bean = metaBean.bean;
if ( metaBean.newObject ) {
if ( structKeyExists( info.metadata, 'constructor' ) ) {
var args = { };
for ( var arg in info.metadata.constructor ) {
accumulator.dependencies[ beanName ][ arg ] = true;
var argBean = { };
// handle known required arguments
if ( info.metadata.constructor[ arg ] ) {
var beanMissing = true;
if ( structKeyExists( overrides, arg ) ) {
args[ arg ] = overrides[ arg ];
beanMissing = false;
} else if ( containsBean( arg ) ) {
argBean = resolveBeanCreate( arg, accumulator );
if ( structKeyExists( argBean, 'bean' ) ) {
args[ arg ] = argBean.bean;
beanMissing = false;
}
}
if ( beanMissing ) {
throw 'bean not found: #arg#; while resolving constructor arguments for #beanName#';
}
} else {
if ( structKeyExists( overrides, arg ) ) {
args[ arg ] = overrides[ arg ];
} else if ( containsBean( arg ) ) {
// optional but present
argBean = resolveBeanCreate( arg, accumulator );
if ( structKeyExists( argBean, 'bean' ) ) {
args[ arg ] = argBean.bean;
}
} else {
// optional but not present
}
}
}
var __ioc_newBean = evaluate( 'bean.init( argumentCollection = args )' );
// if the constructor returns anything, it becomes the bean
// this allows for smart constructors that return things other
// than the CFC being created, such as implicit factory beans
// and automatic singletons etc (rare practices in CFML but...)
if ( !isNull( __ioc_newBean ) ) {
bean = __ioc_newBean;
forceCache( bean, beanName );
}
}
}
/*******************************************************/
if ( !structKeyExists( accumulator.injection, beanName ) ) {
if ( !structKeyExists( variables.settersInfo, beanName ) ) {
variables.settersInfo[ beanName ] = findSetters( bean, info.metadata );
}
var setterMeta = {
setters = variables.settersInfo[ beanName ].setters,
bean = bean,
overrides = overrides
};
accumulator.injection[ beanName ] = setterMeta;
for ( var property in setterMeta.setters ) {
accumulator.dependencies[ beanName ][ property ] = true;
if ( structKeyExists( overrides, property ) ) {
// skip resolution because we'll inject override
} else {
resolveBeanCreate( property, accumulator );
}
}
}
accumulator.bean = bean;
} else if ( isConstant( beanName ) ) {
accumulator.bean = info.value;
accumulator.injection[ beanName ] = { bean = info.value, setters = { } };
} else if ( structKeyExists( info, 'factory' ) ) {
var fmBean = isSimpleValue( info.factory ) ? this.getBean( info.factory ) : info.factory;
var nArgs = arrayLen( info.args );
var argStruct = { };
for ( var i = 1; i <= nArgs; ++i ) {
var argName = info.args[ i ];
if ( structKeyExists( info.overrides, argName ) ) {
argStruct[ i ] = info.overrides[ argName ];
} else {
argStruct[ i ] = this.getBean( argName );
}
}
accumulator.bean = evaluate( 'fmBean.#info.method#(argumentCollection=argStruct)' );
accumulator.injection[ beanName ] = { bean = accumulator.bean, setters = { } };
} else {
throw 'internal error: invalid metadata for #beanName#';
}
} else if ( structKeyExists( variables, 'parent' ) && variables.parent.containsBean( beanName ) ) {
bean = variables.parent.getBean( beanName );
accumulator.injection[ beanName ] = { bean = bean, setters = { } };
accumulator.bean = bean;
} else {
missingBean( beanName );
}
return accumulator;
}
private void function setupFrameworkDefaults() {
param name = "variables.config.recurse" default = true;
param name = "variables.config.strict" default = false;
if ( !structKeyExists( variables.config, 'exclude' ) ) {
variables.config.exclude = [ ];
}
for ( var elem in variables.autoExclude ) {
arrayAppend( variables.config.exclude, replace( elem, chr(92), '/', 'all' ) );
}
// install bean factory constant:
variables.beanInfo.beanFactory = { value = this, isSingleton = true };
if ( structKeyExists( variables.config, 'constants' ) ) {
for ( var beanName in variables.config.constants ) {
variables.beanInfo[ beanName ] = { value = variables.config.constants[ beanName ], isSingleton = true };
}
}
variables.transients = { };
if ( structKeyExists( variables.config, 'transients' ) ) {
for ( var transientFolder in variables.config.transients ) {
variables.transients[ transientFolder ] = true;
}
}
if ( structKeyExists( variables.config, 'singletonPattern' ) &&
structKeyExists( variables.config, 'transientPattern' ) ) {
throw 'singletonPattern and transientPattern are mutually exclusive';
}
if ( !structKeyExists( variables.config, 'omitTypedProperties' ) ) {
variables.config.omitTypedProperties = false;
}
if ( !structKeyExists( variables.config, 'omitDirectoryAliases' ) ) {
variables.config.omitDirectoryAliases = false;
}
if ( !structKeyExists( variables.config, 'singulars' ) ) {
variables.config.singulars = { };
}
if ( !structKeyExists( variables.config, 'liberal' ) ) {
variables.config.liberal = false;
}
variables.config.version = variables._di1_version;
}
// hook for extension points to process beans after they have been
// constructed and injected, but before init-method is called on anything
private void function setupInitMethod( string name, any bean ) {
}
private string function singular( string plural ) {
if ( structKeyExists( variables.config.singulars, plural ) ) {
return variables.config.singulars[ plural ];
}
var single = plural;
var n = len( plural );
var last = right( plural, 1 );
if ( last == 's' ) {
if ( variables.config.liberal && n > 3 && right( plural, 3 ) == 'ies' ) {
single = left( plural, n - 3 ) & 'y';
} else {
single = left( plural, n - 1 );
}
}
return single;
}
}
-9
View File
@@ -1,9 +0,0 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/basicauth">
<cffunction name="get">
<cfargument name="username" default="" />
<cfargument name="password" default="" />
<cfreturn representationOf(arguments) />
</cffunction>
</cfcomponent>
@@ -1,11 +0,0 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/conflict/{URI}, /conflict/{URI}" hint="I have multiple uris">
<cffunction name="get">
<cfargument name="uri" type="string" default="0" />
<cfset local.res = {} />
<cfset local.res.uri = arguments.uri />
<cfreturn representationOf(local.res) />
</cffunction>
</cfcomponent>
-27
View File
@@ -1,27 +0,0 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/echo/{id}" hint="I am the component hint.">
<cfproperty name="dependency1" />
<cffunction name="get" hint="I am the function hint.">
<cfargument name="id" hint="I am the argument hint." />
<cfset local.headers = {}/>
<cfset local.headers['x-dude'] = "dude!" />
<cfset local.res = {} />
<cfset local.res["id"] = arguments.id />
<cfif structKeyExists(arguments, "dataFromOTR")>
<cfset local.res.dataFromOTR = arguments.dataFromOTR />
</cfif>
<cfreturn representationOf(local.res).withStatus(999).withHeaders(local.headers) />
</cffunction>
<cffunction name="put">
<cfargument name="id" />
<cfreturn representationOf(arguments).withStatus(200) />
</cffunction>
<cffunction name="post">
<cfargument name="id" />
<cfreturn representationOf(arguments).withStatus(200) />
</cffunction>
</cfcomponent>
@@ -1,38 +0,0 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/echo/tunnel/{id}">
<cffunction name="get">
<cfargument name="id" />
<cfset var echo = {} />
<cfset echo.actualMethod = "get" />
<cfreturn representationOf(echo) />
</cffunction>
<cffunction name="put">
<cfargument name="id" />
<cfset var echo = {} />
<cfset echo.actualMethod = "put" />
<cfreturn representationOf(echo).withStatus(200) />
</cffunction>
<cffunction name="post">
<cfargument name="id" />
<cfset var echo = {} />
<cfset echo.actualMethod = "post" />
<cfreturn representationOf(echo).withStatus(201) />
</cffunction>
<cffunction name="delete">
<cfargument name="id" />
<cfset var echo = {} />
<cfset echo.actualMethod = "delete" />
<cfreturn representationOf(echo).withStatus(200) />
</cffunction>
</cfcomponent>
@@ -1,26 +0,0 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/echo_regex/{id:\d{5}}">
<cffunction name="get">
<cfargument name="id" />
<cfset local.headers = {}/>
<cfset local.headers['x-dude'] = "dude!" />
<cfreturn representationOf(arguments).withStatus(999).withHeaders(local.headers) />
</cffunction>
<cffunction name="put">
<cfargument name="id" />
<cfreturn representationOf(arguments).withStatus(200) />
</cffunction>
<cffunction name="post">
<cfargument name="id" />
<cfreturn representationOf(arguments).withStatus(200) />
</cffunction>
<cffunction name="setDependency2">
<cfargument name="dependency2" />
<cfset this.dependency2 = arguments.dependency2 />
</cffunction>
</cfcomponent>
-21
View File
@@ -1,21 +0,0 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/echo_alias/{id}, /echo_alias" hint="I have multiple uris">
<cffunction name="get">
<cfargument name="id" default="0" />
<cfset local.res = {} />
<cfset local.res.id = arguments.id />
<cfreturn representationOf(local.res) />
</cffunction>
<cffunction name="put">
<cfargument name="id" />
<cfreturn representationOf(arguments).withStatus(200) />
</cffunction>
<cffunction name="post">
<cfargument name="id" />
<cfreturn representationOf(arguments).withStatus(200) />
</cffunction>
</cfcomponent>
@@ -0,0 +1,14 @@
component extends="core.resource" taffy:uri="/users/{userId}/orders/{orderId}" {
/**
* GET /users/{userId}/orders/{orderId} - Returns order for a user
*/
function get(required string userId, required string orderId) {
return rep({
userId: arguments.userId,
orderId: arguments.orderId,
method: "GET"
});
}
}
@@ -0,0 +1,18 @@
component extends="core.resource" taffy:uri="/simple" {
/**
* GET /simple - Returns a simple response
*/
function get() {
return rep({ message: "Hello from simple resource", method: "GET" });
}
/**
* POST /simple - Creates something
*/
function post(string name = "") {
return rep({ message: "Created", name: arguments.name, method: "POST" })
.withStatus(201, "Created");
}
}
@@ -0,0 +1,24 @@
component extends="core.resource" taffy:uri="/items/{id}" {
/**
* GET /items/{id} - Returns an item by ID
*/
function get(required string id) {
return rep({ id: arguments.id, name: "Item #arguments.id#", method: "GET" });
}
/**
* PUT /items/{id} - Updates an item
*/
function put(required string id, string name = "") {
return rep({ id: arguments.id, name: arguments.name, updated: true, method: "PUT" });
}
/**
* DELETE /items/{id} - Deletes an item
*/
function delete(required string id) {
return rep({ id: arguments.id, deleted: true, method: "DELETE" });
}
}
@@ -0,0 +1,24 @@
component extends="core.resource" taffy:uri="/custom" {
/**
* Standard GET method
*/
function get() {
return rep({ message: "Standard GET", method: "GET" });
}
/**
* Custom PATCH method using taffy_verb metadata
*/
function updatePartial() taffy_verb="PATCH" {
return rep({ message: "Custom PATCH handler", method: "PATCH" });
}
/**
* Custom OPTIONS method using taffy_verb metadata (underscore style)
*/
function handleOptions() taffy_verb="OPTIONS" {
return rep({ message: "Custom OPTIONS handler", method: "OPTIONS" });
}
}
@@ -1,17 +0,0 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/upload">
<cffunction name="post">
<cfargument name="img" />
<cfset var local = structNew() />
<!--- accept the upload --->
<cffile action="upload" destination="#getTempDirectory()#" filefield="img" result="local.r" />
<!--- delete the file --->
<cffile action="delete" file="#local.r.serverdirectory#/#local.r.serverfile#" />
<cfreturn representationOf( local.r ).withStatus(200) />
</cffunction>
</cfcomponent>
@@ -1,22 +0,0 @@
<cfcomponent extends="taffy.core.nativeJsonSerializer">
<cfproperty name="dependency1" />
<cffunction name="getAsJSON" output="false"
taffy_mime="text/json"
taffy_default="true"
hint="serializes data as JSON">
<cfreturn super.getAsJson() />
</cffunction>
<cffunction name="getAsXML" output="false" taffy:mime="text/xml">
<cfreturn "FAIL FAIL FAIL" />
</cffunction>
<cffunction name="setDependency2">
<cfargument name="dependency2" />
<cfset this.dependency2 = arguments.dependency2 />
</cffunction>
</cfcomponent>
-1
View File
@@ -1 +0,0 @@
<cfcomponent></cfcomponent>
-1
View File
@@ -1 +0,0 @@
<cfcomponent></cfcomponent>
@@ -1,7 +0,0 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/throwException">
<cffunction name="get">
<cfthrow message="this is the exception message." detail="this is the exception detail." />
</cffunction>
</cfcomponent>
@@ -1,7 +0,0 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/uriWithForwardSlash{,},uriAliasWithoutFowardSlash">
<cffunction name="get">
<cfreturn noData() />
</cffunction>
</cfcomponent>
-7
View File
@@ -1,7 +0,0 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/echo/towel">
<cffunction name="get">
<cfreturn representationOf("don't panic!").withStatus(200) />
</cffunction>
</cfcomponent>
@@ -1,7 +0,0 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="uriWithoutForwardSlash">
<cffunction name="get">
<cfreturn noData() />
</cffunction>
</cfcomponent>
@@ -1,5 +0,0 @@
<cfcomponent extends="taffy.core.resource">
<cfif structKeyExists(request, "_testsRunning")>
<cfthrow message="Fail">
</cfif>
</cfcomponent>
+23
View File
@@ -0,0 +1,23 @@
<cfscript>
/**
* TestBox Runner for Taffy Framework Tests
*
* Run this file via browser or use CommandBox:
* box testbox run
*/
// Create TestBox instance
testBox = new testbox.system.TestBox();
// Run all specs in the specs directory
results = testBox.run(
directory = {
recurse: true,
mapping: "tests.specs"
},
reporter = url.reporter ?: "simple"
);
// Output the results
writeOutput(results);
</cfscript>
@@ -1 +0,0 @@
I am some non-framework-related file. woot.
+173
View File
@@ -0,0 +1,173 @@
component extends="testbox.system.BaseSpec" {
function beforeAll() {
// Build base URL from the server running these tests
var port = cgi.server_port;
var host = cgi.server_name;
if (!len(host) || host == "") host = "localhost";
variables.baseURL = "http://#host#:#port#/tests/testapi/index.cfm";
}
/**
* Helper: make an HTTP request to the test API
*/
private struct function apiRequest(
required string uri,
string method = "GET",
string body = "",
struct headers = {}
) {
var result = {};
var fullURL = variables.baseURL & "?endpoint=" & arguments.uri;
cfhttp(url=fullURL, method=arguments.method, result="result", timeout=10, charset="utf-8") {
cfhttpparam(type="header", name="Content-Type", value="application/json");
cfhttpparam(type="header", name="Accept", value="application/json");
for (var h in arguments.headers) {
cfhttpparam(type="header", name=h, value=arguments.headers[h]);
}
if (len(arguments.body)) {
cfhttpparam(type="body", value=arguments.body);
}
}
result.data = {};
if (structKeyExists(result, "fileContent")) {
var body = result.fileContent;
if (!isSimpleValue(body)) {
body = toString(body);
}
body = trim(body);
if (len(body) && isJSON(body)) {
result.data = deserializeJSON(body);
}
}
return result;
}
function run() {
describe("End-to-End HTTP Tests", function() {
describe("GET requests", function() {
it("should return 200 for a valid GET request", function() {
var res = apiRequest("/echo");
expect(res.status_code).toBe(200);
expect(res.data.method).toBe("GET");
expect(res.data.message).toBe("echo");
});
it("should extract URI token from path", function() {
var res = apiRequest("/echo/42");
expect(res.status_code).toBe(200);
expect(res.data.id).toBe("42");
expect(res.data.method).toBe("GET");
});
it("should extract multiple URI tokens", function() {
var res = apiRequest("/echo/10/child/20");
expect(res.status_code).toBe(200);
expect(res.data.parentId).toBe("10");
expect(res.data.childId).toBe("20");
});
it("should return JSON content type", function() {
var res = apiRequest("/echo");
expect(res.responseheader["Content-Type"]).toInclude("application/json");
});
});
describe("POST requests", function() {
it("should return 201 Created", function() {
var res = apiRequest(
uri = "/echo",
method = "POST",
body = '{"name":"test","value":"123"}'
);
expect(res.status_code).toBe(201);
expect(res.data.method).toBe("POST");
});
it("should deserialize JSON request body", function() {
var res = apiRequest(
uri = "/echo",
method = "POST",
body = '{"name":"widget","value":"abc"}'
);
expect(res.data.name).toBe("widget");
expect(res.data.value).toBe("abc");
});
});
describe("PUT requests", function() {
it("should handle PUT with URI token and body", function() {
var res = apiRequest(
uri = "/echo/55",
method = "PUT",
body = '{"name":"updated"}'
);
expect(res.status_code).toBe(200);
expect(res.data.method).toBe("PUT");
expect(res.data.id).toBe("55");
expect(res.data.name).toBe("updated");
});
});
describe("DELETE requests", function() {
it("should handle DELETE with URI token", function() {
var res = apiRequest(uri="/echo/99", method="DELETE");
expect(res.status_code).toBe(200);
expect(res.data.method).toBe("DELETE");
expect(res.data.id).toBe("99");
});
it("should return custom response headers", function() {
var res = apiRequest(uri="/echo/99", method="DELETE");
expect(res.responseheader["X-Deleted-Id"]).toBe("99");
});
});
describe("Error responses", function() {
it("should return 404 for unknown URI", function() {
var res = apiRequest("/nonexistent/path");
expect(res.status_code).toBe(404);
});
it("should return 405 for unsupported HTTP method", function() {
// /echo/{parentId}/child/{childId} only supports GET
var res = apiRequest(uri="/echo/1/child/2", method="DELETE");
expect(res.status_code).toBe(405);
});
});
describe("X-HTTP-Method-Override", function() {
it("should support method tunneling via header", function() {
var res = apiRequest(
uri = "/echo/77",
method = "POST",
headers = { "X-HTTP-Method-Override": "PUT" },
body = '{"name":"tunneled"}'
);
expect(res.data.method).toBe("PUT");
expect(res.data.id).toBe("77");
expect(res.data.name).toBe("tunneled");
});
});
});
}
}
+84
View File
@@ -0,0 +1,84 @@
component extends="testbox.system.BaseSpec" {
function beforeAll() {
var port = cgi.server_port;
var host = cgi.server_name;
if (!len(host) || host == "") host = "localhost";
variables.baseURL = "http://#host#:#port#/tests/testapi/index.cfm";
}
private struct function fetchSpec(string param = "openapi") {
var result = {};
cfhttp(url="#variables.baseURL#?#arguments.param#", method="GET", result="result", timeout=10, charset="utf-8");
return result;
}
function run() {
describe("OpenAPI 3.1 spec endpoint", function() {
it("returns JSON with openapi version 3.1.0", function() {
var res = fetchSpec("openapi");
expect(res.status_code).toBe(200);
expect(isJSON(res.fileContent)).toBeTrue("Expected JSON, got: " & left(res.fileContent, 300));
var spec = deserializeJSON(res.fileContent);
expect(spec.openapi).toBe("3.1.0");
});
it("includes info.title and info.version", function() {
var spec = deserializeJSON(fetchSpec().fileContent);
expect(spec).toHaveKey("info");
expect(spec.info).toHaveKey("title");
expect(spec.info).toHaveKey("version");
});
it("documents /echo/{id} with GET, PUT, DELETE", function() {
var spec = deserializeJSON(fetchSpec().fileContent);
expect(spec).toHaveKey("paths");
expect(spec.paths).toHaveKey("/echo/{id}");
var echo = spec.paths["/echo/{id}"];
expect(echo).toHaveKey("get");
expect(echo).toHaveKey("put");
expect(echo).toHaveKey("delete");
});
it("marks path token 'id' as required in:path on GET", function() {
var spec = deserializeJSON(fetchSpec().fileContent);
var params = spec.paths["/echo/{id}"].get.parameters;
var found = false;
for (var i = 1; i <= arrayLen(params); i++) {
if (params[i].name == "id" && params[i]["in"] == "path" && params[i].required == true) {
found = true;
break;
}
}
expect(found).toBeTrue("Expected required path parameter 'id' on GET /echo/{id}");
});
it("emits requestBody with json + form-urlencoded for PUT body params", function() {
var spec = deserializeJSON(fetchSpec().fileContent);
var put = spec.paths["/echo/{id}"].put;
expect(put).toHaveKey("requestBody");
expect(put.requestBody.content).toHaveKey("application/json");
expect(put.requestBody.content).toHaveKey("application/x-www-form-urlencoded");
var props = put.requestBody.content["application/json"].schema.properties;
expect(props).toHaveKey("name");
});
it("includes responses.200 for each operation", function() {
var spec = deserializeJSON(fetchSpec().fileContent);
expect(spec.paths["/echo/{id}"].get).toHaveKey("responses");
expect(spec.paths["/echo/{id}"].get.responses).toHaveKey("200");
});
it("accepts ?swagger alias", function() {
var res = fetchSpec("swagger");
expect(res.status_code).toBe(200);
expect(deserializeJSON(res.fileContent).openapi).toBe("3.1.0");
});
});
}
}
@@ -0,0 +1,228 @@
component extends="testbox.system.BaseSpec" {
function beforeAll() {
// Save existing application context so we can restore it after
if (structKeyExists(application, "_taffy")) {
variables.savedTaffy = duplicate(application._taffy);
}
// Create a fresh API instance and bootstrap it to populate application._taffy with defaults
variables.api = new core.api();
variables.api.onApplicationStart();
variables.settings = application._taffy.settings;
}
function afterAll() {
// Restore original application context
if (structKeyExists(variables, "savedTaffy")) {
application._taffy = variables.savedTaffy;
}
}
function run() {
describe("Configuration", function() {
describe("Default settings", function() {
it("should have reloadKey default of 'reload'", function() {
expect(settings.reloadKey).toBe("reload");
});
it("should have reloadPassword default of 'true'", function() {
expect(settings.reloadPassword).toBe("true");
});
it("should have reloadOnEveryRequest default of false", function() {
expect(settings.reloadOnEveryRequest).toBeFalse();
});
it("should have disableDashboard default of false", function() {
expect(settings.disableDashboard).toBeFalse();
});
it("should have disabledDashboardRedirect default of empty string", function() {
expect(settings.disabledDashboardRedirect).toBe("");
});
it("should have showDocsWhenDashboardDisabled default of false", function() {
expect(settings.showDocsWhenDashboardDisabled).toBeFalse();
});
it("should have returnExceptionsAsJson default of false", function() {
expect(settings.returnExceptionsAsJson).toBeFalse();
});
it("should have exposeTaffyHeaders default of true", function() {
expect(settings.exposeTaffyHeaders).toBeTrue();
});
it("should have allowCrossDomain default of false", function() {
expect(settings.allowCrossDomain).toBeFalse();
});
it("should have useEtags default of false", function() {
expect(settings.useEtags).toBeFalse();
});
it("should have jsonp default of false", function() {
expect(settings.jsonp).toBeFalse();
});
it("should have noDataSends204NoContent default of false", function() {
expect(settings.noDataSends204NoContent).toBeFalse();
});
it("should have endpointURLParam default of 'endpoint'", function() {
expect(settings.endpointURLParam).toBe("endpoint");
});
it("should have debugKey default of 'debug'", function() {
expect(settings.debugKey).toBe("debug");
});
it("should have unhandledPaths default of '/flex2gateway'", function() {
expect(settings.unhandledPaths).toBe("/flex2gateway");
});
it("should have globalHeaders default to empty struct", function() {
expect(settings.globalHeaders).toBeStruct();
expect(structIsEmpty(settings.globalHeaders)).toBeTrue();
});
it("should have exceptionLogAdapter default of LogToDevNull", function() {
expect(settings.exceptionLogAdapter).toBe("taffy.bonus.LogToDevNull");
});
it("should have allowGoogleFonts default of true", function() {
expect(settings.allowGoogleFonts).toBeTrue();
});
});
describe("Serializer configuration", function() {
it("should use nativeJsonSerializer by default", function() {
expect(settings.serializer).toBe("taffy.core.nativeJsonSerializer");
});
it("should have nativeJsonSerializer extend baseSerializer", function() {
var serializer = new core.nativeJsonSerializer();
expect(new tests.helpers.MetadataHelper().extendsClass(serializer, "baseSerializer")).toBeTrue();
});
});
describe("Deserializer configuration", function() {
it("should use nativeJsonDeserializer by default", function() {
expect(settings.deserializer).toBe("taffy.core.nativeJsonDeserializer");
});
it("should have nativeJsonDeserializer extend baseDeserializer", function() {
var deserializer = new core.nativeJsonDeserializer();
expect(new tests.helpers.MetadataHelper().extendsClass(deserializer, "baseDeserializer")).toBeTrue();
});
it("should support JSON content type via getFromJson", function() {
var deserializer = new core.nativeJsonDeserializer();
var meta = getMetadata(deserializer);
var hasJsonSupport = false;
for (var func in meta.functions) {
if (func.name == "getFromJson") {
hasJsonSupport = true;
break;
}
}
expect(hasJsonSupport).toBeTrue();
});
it("should support form-urlencoded content type via getFromForm", function() {
var deserializer = new core.baseDeserializer();
var meta = getMetadata(deserializer);
var hasFormSupport = false;
for (var func in meta.functions) {
if (func.name == "getFromForm") {
hasFormSupport = true;
break;
}
}
expect(hasFormSupport).toBeTrue();
});
});
describe("MIME type configuration", function() {
it("should detect mime types from serializer metadata", function() {
var serializer = new core.nativeJsonSerializer();
var meta = getMetadata(serializer);
var jsonMime = "";
for (var func in meta.functions) {
if (func.name == "getAsJson") {
if (structKeyExists(func, "taffy:mime")) {
jsonMime = func["taffy:mime"];
} else if (structKeyExists(func, "taffy_mime")) {
jsonMime = func["taffy_mime"];
}
break;
}
}
expect(jsonMime).toInclude("application/json");
});
it("should identify default mime type from serializer", function() {
var serializer = new core.nativeJsonSerializer();
var meta = getMetadata(serializer);
var isDefault = false;
for (var func in meta.functions) {
if (func.name == "getAsJson") {
if (structKeyExists(func, "taffy:default")) {
isDefault = func["taffy:default"];
} else if (structKeyExists(func, "taffy_default")) {
isDefault = func["taffy_default"];
}
break;
}
}
expect(isDefault).toBeTrue();
});
});
describe("Version", function() {
it("should report version 4.0.0", function() {
expect(application._taffy.version).toBe("4.0.0");
});
});
describe("Unhandled paths regex", function() {
it("should convert unhandledPaths to regex", function() {
expect(structKeyExists(settings, "unhandledPathsRegex")).toBeTrue();
expect(len(settings.unhandledPathsRegex)).toBeGT(0);
});
it("should match the default /flex2gateway path", function() {
expect(reFindNoCase("^(" & settings.unhandledPathsRegex & ")", "/flex2gateway")).toBeGT(0);
});
});
describe("Content types", function() {
it("should have registered supported content types", function() {
expect(structKeyExists(application._taffy, "contentTypes")).toBeTrue();
expect(application._taffy.contentTypes).toBeStruct();
});
});
});
}
}
@@ -0,0 +1,220 @@
component extends="testbox.system.BaseSpec" {
function beforeAll() {
// Initialize application scope with minimal settings required by resource.cfc
application._taffy = {
settings: {
serializer: "core.nativeJsonSerializer",
noDataSends204NoContent: false
},
factory: new core.factory(),
compat: {
queryToArray: "missing",
queryToStruct: "missing"
}
};
}
function run() {
describe("HTTP Methods", function() {
describe("Standard HTTP verb mapping", function() {
it("should recognize GET method in resource", function() {
var resource = new tests.resources.MockSimpleResource();
var meta = getMetadata(resource);
var hasGet = false;
for (var func in meta.functions) {
if (func.name == "get") {
hasGet = true;
break;
}
}
expect(hasGet).toBeTrue();
});
it("should recognize POST method in resource", function() {
var resource = new tests.resources.MockSimpleResource();
var meta = getMetadata(resource);
var hasPost = false;
for (var func in meta.functions) {
if (func.name == "post") {
hasPost = true;
break;
}
}
expect(hasPost).toBeTrue();
});
it("should recognize PUT method in resource", function() {
var resource = new tests.resources.MockTokenResource();
var meta = getMetadata(resource);
var hasPut = false;
for (var func in meta.functions) {
if (func.name == "put") {
hasPut = true;
break;
}
}
expect(hasPut).toBeTrue();
});
it("should recognize DELETE method in resource", function() {
var resource = new tests.resources.MockTokenResource();
var meta = getMetadata(resource);
var hasDelete = false;
for (var func in meta.functions) {
if (func.name == "delete") {
hasDelete = true;
break;
}
}
expect(hasDelete).toBeTrue();
});
});
describe("taffy:verb metadata override", function() {
it("should recognize taffy:verb=PATCH on custom method", function() {
var resource = new tests.resources.MockVerbOverrideResource();
var meta = getMetadata(resource);
var patchFunc = "";
for (var func in meta.functions) {
if (func.name == "updatePartial") {
patchFunc = func;
break;
}
}
expect(patchFunc).notToBeEmpty();
var hasVerbMeta = structKeyExists(patchFunc, "taffy:verb")
|| structKeyExists(patchFunc, "taffy_verb");
expect(hasVerbMeta).toBeTrue();
var verbValue = structKeyExists(patchFunc, "taffy:verb")
? patchFunc["taffy:verb"]
: patchFunc["taffy_verb"];
expect(verbValue).toBe("PATCH");
});
it("should recognize taffy_verb (underscore style) on custom method", function() {
var resource = new tests.resources.MockVerbOverrideResource();
var meta = getMetadata(resource);
var optionsFunc = "";
for (var func in meta.functions) {
if (func.name == "handleOptions") {
optionsFunc = func;
break;
}
}
expect(optionsFunc).notToBeEmpty();
var hasVerbMeta = structKeyExists(optionsFunc, "taffy:verb")
|| structKeyExists(optionsFunc, "taffy_verb");
expect(hasVerbMeta).toBeTrue();
});
});
describe("Resource taffy:uri metadata", function() {
it("should have taffy:uri on MockSimpleResource", function() {
var resource = new tests.resources.MockSimpleResource();
var meta = getMetadata(resource);
var hasUri = structKeyExists(meta, "taffy:uri")
|| structKeyExists(meta, "taffy_uri");
expect(hasUri).toBeTrue();
});
it("should have correct URI pattern on MockTokenResource", function() {
var resource = new tests.resources.MockTokenResource();
var meta = getMetadata(resource);
var uri = structKeyExists(meta, "taffy:uri")
? meta["taffy:uri"]
: meta["taffy_uri"];
expect(uri).toBe("/items/{id}");
});
it("should have correct URI pattern on MockMultiTokenResource", function() {
var resource = new tests.resources.MockMultiTokenResource();
var meta = getMetadata(resource);
var uri = structKeyExists(meta, "taffy:uri")
? meta["taffy:uri"]
: meta["taffy_uri"];
expect(uri).toBe("/users/{userId}/orders/{orderId}");
});
});
describe("Resource inheritance", function() {
it("should extend core.resource", function() {
var resource = new tests.resources.MockSimpleResource();
expect(new tests.helpers.MetadataHelper().extendsClass(resource, "resource")).toBeTrue();
});
it("should have access to rep() method from parent", function() {
var resource = new tests.resources.MockSimpleResource();
expect(resource).toHaveKey("rep");
});
it("should have access to representationOf() method from parent", function() {
var resource = new tests.resources.MockSimpleResource();
expect(resource).toHaveKey("representationOf");
});
});
describe("Resource method invocation", function() {
it("should execute GET and return serializer", function() {
var resource = new tests.resources.MockSimpleResource();
var result = resource.get();
expect(new tests.helpers.MetadataHelper().extendsClass(result, "baseSerializer")).toBeTrue();
expect(result.getData().method).toBe("GET");
});
it("should execute POST with arguments", function() {
var resource = new tests.resources.MockSimpleResource();
var result = resource.post(name = "TestItem");
expect(new tests.helpers.MetadataHelper().extendsClass(result, "baseSerializer")).toBeTrue();
expect(result.getData().name).toBe("TestItem");
expect(result.getStatus()).toBe(201);
});
it("should execute GET with token parameter", function() {
var resource = new tests.resources.MockTokenResource();
var result = resource.get(id = "123");
expect(result.getData().id).toBe("123");
expect(result.getData().method).toBe("GET");
});
it("should execute PUT with token and body parameters", function() {
var resource = new tests.resources.MockTokenResource();
var result = resource.put(id = "456", name = "Updated");
expect(result.getData().id).toBe("456");
expect(result.getData().name).toBe("Updated");
expect(result.getData().updated).toBeTrue();
});
it("should execute DELETE with token parameter", function() {
var resource = new tests.resources.MockTokenResource();
var result = resource.delete(id = "789");
expect(result.getData().id).toBe("789");
expect(result.getData().deleted).toBeTrue();
});
it("should execute GET with multiple tokens", function() {
var resource = new tests.resources.MockMultiTokenResource();
var result = resource.get(userId = "1", orderId = "2");
expect(result.getData().userId).toBe("1");
expect(result.getData().orderId).toBe("2");
});
});
});
}
}
@@ -0,0 +1,239 @@
component extends="testbox.system.BaseSpec" {
function run() {
describe("Hooks", function() {
describe("onTaffyRequest hook", function() {
it("should be defined in api.cfc", function() {
var api = new core.api();
expect(api).toHaveKey("onTaffyRequest");
});
it("should accept required parameters", function() {
var api = new core.api();
var meta = getMetadata(api);
var hookFunc = "";
for (var func in meta.functions) {
if (func.name == "onTaffyRequest") {
hookFunc = func;
break;
}
}
expect(hookFunc).notToBeEmpty();
expect(hookFunc.parameters).toBeArray();
// Should have all expected parameters
var paramNames = [];
for (var param in hookFunc.parameters) {
arrayAppend(paramNames, param.name);
}
expect(arrayFindNoCase(paramNames, "verb")).toBeGT(0);
expect(arrayFindNoCase(paramNames, "cfc")).toBeGT(0);
expect(arrayFindNoCase(paramNames, "requestArguments")).toBeGT(0);
expect(arrayFindNoCase(paramNames, "mimeExt")).toBeGT(0);
expect(arrayFindNoCase(paramNames, "headers")).toBeGT(0);
expect(arrayFindNoCase(paramNames, "methodMetadata")).toBeGT(0);
expect(arrayFindNoCase(paramNames, "matchedURI")).toBeGT(0);
});
it("should return true by default", function() {
var api = new core.api();
var result = api.onTaffyRequest(
verb = "GET",
cfc = "testResource",
requestArguments = {},
mimeExt = "json",
headers = {},
methodMetadata = {},
matchedURI = "/test"
);
expect(result).toBeTrue();
});
});
describe("onTaffyRequestEnd hook", function() {
it("should be defined in api.cfc", function() {
var api = new core.api();
expect(api).toHaveKey("onTaffyRequestEnd");
});
it("should accept required parameters", function() {
var api = new core.api();
var meta = getMetadata(api);
var hookFunc = "";
for (var func in meta.functions) {
if (func.name == "onTaffyRequestEnd") {
hookFunc = func;
break;
}
}
expect(hookFunc).notToBeEmpty();
var paramNames = [];
for (var param in hookFunc.parameters) {
arrayAppend(paramNames, param.name);
}
// Should include response-related parameters
expect(arrayFindNoCase(paramNames, "parsedResponse")).toBeGT(0);
expect(arrayFindNoCase(paramNames, "originalResponse")).toBeGT(0);
expect(arrayFindNoCase(paramNames, "statusCode")).toBeGT(0);
});
it("should return true by default", function() {
var api = new core.api();
var result = api.onTaffyRequestEnd(
verb = "GET",
cfc = "testResource",
requestArguments = {},
mimeExt = "json",
headers = {},
methodMetadata = {},
matchedURI = "/test",
parsedResponse = '{"test":true}',
originalResponse = { test: true },
statusCode = 200
);
expect(result).toBeTrue();
});
});
describe("Caching hooks", function() {
it("should have validCacheExists hook", function() {
var api = new core.api();
expect(api).toHaveKey("validCacheExists");
});
it("validCacheExists should return false by default", function() {
var api = new core.api();
var result = api.validCacheExists(cacheKey = "test-key");
expect(result).toBeFalse();
});
it("should have setCachedResponse hook", function() {
var api = new core.api();
expect(api).toHaveKey("setCachedResponse");
});
it("should have getCachedResponse hook", function() {
var api = new core.api();
expect(api).toHaveKey("getCachedResponse");
});
it("should have getCacheKey hook", function() {
var api = new core.api();
expect(api).toHaveKey("getCacheKey");
});
it("getCacheKey should generate key from request params", function() {
var api = new core.api();
var requestArgs = { id: 123 };
var result = api.getCacheKey(
cfc = "testResource",
requestArguments = requestArgs,
matchedURI = "/items/{id}"
);
expect(result).toInclude("/items/{id}");
});
});
describe("getEnvironment hook", function() {
it("should be defined in api.cfc", function() {
var api = new core.api();
expect(api).toHaveKey("getEnvironment");
});
it("should return empty string by default", function() {
var api = new core.api();
var result = api.getEnvironment();
expect(result).toBe("");
});
});
describe("Hook extensibility", function() {
it("should allow overriding onTaffyRequest in subclass", function() {
// Any class extending core.api can override hooks
var api = new core.api();
// The base api has the hook that can be overridden
expect(api).toHaveKey("onTaffyRequest");
// Verify it's a function that can be overridden
expect(isCustomFunction(api.onTaffyRequest)).toBeTrue();
});
});
describe("Basic auth helper", function() {
it("should have getBasicAuthCredentials method", function() {
var api = new core.api();
expect(api).toHaveKey("getBasicAuthCredentials");
});
it("getBasicAuthCredentials should return struct with username and password", function() {
var api = new core.api();
var result = api.getBasicAuthCredentials();
expect(result).toBeStruct();
expect(result).toHaveKey("username");
expect(result).toHaveKey("password");
});
it("should return empty credentials when no auth header", function() {
var api = new core.api();
var result = api.getBasicAuthCredentials();
expect(result.username).toBe("");
expect(result.password).toBe("");
});
});
describe("Helper methods", function() {
it("should have getHostname helper", function() {
var api = new core.api();
expect(api).toHaveKey("getHostname");
var hostname = api.getHostname();
expect(len(hostname)).toBeGT(0);
});
it("should have addHeaders helper", function() {
var api = new core.api();
expect(api).toHaveKey("addHeaders");
});
it("should have rep helper for responses", function() {
var api = new core.api();
expect(api).toHaveKey("rep");
});
it("should have representationOf helper", function() {
var api = new core.api();
expect(api).toHaveKey("representationOf");
});
it("should have noData helper", function() {
var api = new core.api();
expect(api).toHaveKey("noData");
});
it("should have noContent helper", function() {
var api = new core.api();
expect(api).toHaveKey("noContent");
});
});
});
}
}
@@ -0,0 +1,252 @@
component extends="testbox.system.BaseSpec" {
function beforeAll() {
// Set up minimal application context
application._taffy = {
settings: {
serializer: "core.nativeJsonSerializer",
noDataSends204NoContent: false
},
factory: new core.factory(),
compat: {
queryToArray: "missing",
queryToStruct: "missing"
}
};
}
function run() {
describe("Response Handling", function() {
describe("Status codes", function() {
it("should default to 200 OK", function() {
var resource = new tests.resources.MockSimpleResource();
var result = resource.get();
expect(result.getStatus()).toBe(200);
expect(result.getStatusText()).toBe("OK");
});
it("should support 201 Created", function() {
var resource = new tests.resources.MockSimpleResource();
var result = resource.post(name = "Test");
expect(result.getStatus()).toBe(201);
expect(result.getStatusText()).toBe("Created");
});
it("should support 404 Not Found", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setData({ error: "Resource not found" }).withStatus(404);
expect(serializer.getStatus()).toBe(404);
expect(serializer.getStatusText()).toBe("Not Found");
});
it("should support 400 Bad Request", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setData({ error: "Invalid input" }).withStatus(400);
expect(serializer.getStatus()).toBe(400);
expect(serializer.getStatusText()).toBe("Bad Request");
});
it("should support 500 Internal Server Error", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setData({ error: "Something went wrong" }).withStatus(500);
expect(serializer.getStatus()).toBe(500);
expect(serializer.getStatusText()).toBe("Internal Server Error");
});
it("should support custom status text", function() {
var serializer = new core.nativeJsonSerializer();
serializer.withStatus(299, "Custom Success");
expect(serializer.getStatus()).toBe(299);
expect(serializer.getStatusText()).toBe("Custom Success");
});
});
describe("Response headers", function() {
it("should set custom headers", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setData({ id: 123 })
.withHeaders({ "X-Request-Id": "abc123" });
var headers = serializer.getHeaders();
expect(headers["X-Request-Id"]).toBe("abc123");
});
it("should set Location header for created resources", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setData({ id: 456 })
.withStatus(201)
.withHeaders({ "Location": "/items/456" });
expect(serializer.getHeaders()["Location"]).toBe("/items/456");
});
it("should set multiple headers", function() {
var serializer = new core.nativeJsonSerializer();
serializer.withHeaders({
"X-Rate-Limit": "100",
"X-Rate-Remaining": "99",
"X-Cache-Control": "no-cache"
});
var headers = serializer.getHeaders();
expect(headers["X-Rate-Limit"]).toBe("100");
expect(headers["X-Rate-Remaining"]).toBe("99");
expect(headers["X-Cache-Control"]).toBe("no-cache");
});
});
describe("JSON serialization", function() {
it("should serialize response data as JSON", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setData({ message: "Hello", count: 42 });
var json = serializer.getAsJson();
expect(isJSON(json)).toBeTrue();
var parsed = deserializeJSON(json);
expect(parsed.message).toBe("Hello");
expect(parsed.count).toBe(42);
});
it("should serialize nested structures", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setData({
user: {
name: "John",
email: "john@example.com"
},
permissions: ["read", "write"]
});
var json = serializer.getAsJson();
var parsed = deserializeJSON(json);
expect(parsed.user.name).toBe("John");
expect(parsed.permissions).toBeArray();
});
it("should serialize array responses", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setData([
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" }
]);
var json = serializer.getAsJson();
var parsed = deserializeJSON(json);
expect(parsed).toBeArray();
expect(arrayLen(parsed)).toBe(2);
});
it("should handle boolean values correctly", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setData({
active: true,
deleted: false
});
var json = serializer.getAsJson();
var parsed = deserializeJSON(json);
expect(parsed.active).toBeTrue();
expect(parsed.deleted).toBeFalse();
});
});
describe("noData() and noContent()", function() {
it("noData() should return empty response when setting is false", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setNoDataSends204NoContent(false);
var result = serializer.noData();
// Should still be 200 with empty data
expect(result.getStatus()).toBe(200);
});
it("noData() should return 204 when setting is true", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setNoDataSends204NoContent(true);
var result = serializer.noData();
expect(result.getStatus()).toBe(204);
});
it("noContent() should return 204", function() {
var serializer = new core.nativeJsonSerializer();
var result = serializer.noContent();
expect(result.getStatus()).toBe(204);
expect(result.getStatusText()).toBe("No Content");
});
it("noContent() should set Content-Type to text/plain", function() {
var serializer = new core.nativeJsonSerializer();
var result = serializer.noContent();
var headers = result.getHeaders();
expect(headers["Content-Type"]).toBe("text/plain");
});
});
describe("Response type detection", function() {
it("should identify textual response type", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setData({ message: "test" });
expect(serializer.getType()).toBe("textual");
});
it("should identify filename response type", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setFileName("/path/to/file.pdf");
expect(serializer.getType()).toBe("filename");
});
it("should identify filedata response type", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setFileData(toBinary(toBase64("test data")));
expect(serializer.getType()).toBe("filedata");
});
it("should identify imagedata response type", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setImageData(toBinary(toBase64("image data")));
expect(serializer.getType()).toBe("imagedata");
});
});
describe("File streaming responses", function() {
it("should configure file download response", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setFileName("/path/to/report.pdf")
.withMime("application/pdf");
expect(serializer.getFileName()).toBe("/path/to/report.pdf");
expect(serializer.getFileMime()).toBe("application/pdf");
expect(serializer.getType()).toBe("filename");
});
it("should configure binary data response", function() {
var binaryData = toBinary(toBase64("PDF content here"));
var serializer = new core.nativeJsonSerializer();
serializer.setFileData(binaryData)
.withMime("application/pdf");
expect(serializer.getFileData()).toBe(binaryData);
expect(serializer.getFileMime()).toBe("application/pdf");
});
it("should configure file delete after download", function() {
var serializer = new core.nativeJsonSerializer();
serializer.setFileName("/tmp/temp-report.pdf")
.withMime("application/pdf")
.andDelete(true);
expect(serializer.getDeleteFile()).toBeTrue();
});
});
});
}
}
@@ -0,0 +1,169 @@
component extends="testbox.system.BaseSpec" {
function beforeAll() {
// Initialize application scope with minimal settings required by resource.cfc
application._taffy = {
settings: {
serializer: "core.nativeJsonSerializer",
noDataSends204NoContent: false
},
factory: new core.factory(),
compat: {
queryToArray: "missing",
queryToStruct: "missing"
}
};
// Create a mock API instance for testing URI methods
variables.api = new core.api();
}
function run() {
describe("URI Matching", function() {
describe("convertURItoRegex()", function() {
it("should convert simple static URI to regex", function() {
var result = makePublic(api, "convertURItoRegex").convertURItoRegex("/users");
expect(result).toBeStruct();
expect(result).toHaveKey("uriRegex");
expect(result).toHaveKey("tokens");
expect(result.tokens).toBeArray();
expect(arrayLen(result.tokens)).toBe(0);
});
it("should extract single token from URI", function() {
var result = makePublic(api, "convertURItoRegex").convertURItoRegex("/users/{id}");
expect(arrayLen(result.tokens)).toBe(1);
expect(result.tokens[1]).toBe("id");
});
it("should extract multiple tokens from URI", function() {
var result = makePublic(api, "convertURItoRegex").convertURItoRegex("/users/{userId}/orders/{orderId}");
expect(arrayLen(result.tokens)).toBe(2);
expect(result.tokens[1]).toBe("userId");
expect(result.tokens[2]).toBe("orderId");
});
it("should handle mixed static and token segments", function() {
var result = makePublic(api, "convertURItoRegex").convertURItoRegex("/api/v1/users/{id}/profile");
expect(arrayLen(result.tokens)).toBe(1);
expect(result.tokens[1]).toBe("id");
});
it("should generate regex that matches expected URIs", function() {
var result = makePublic(api, "convertURItoRegex").convertURItoRegex("/items/{id}");
var regex = result.uriRegex;
// Should match /items/123
expect(reFindNoCase(regex, "/items/123")).toBeGT(0);
// Should match /items/abc
expect(reFindNoCase(regex, "/items/abc")).toBeGT(0);
// Should match with format extension
expect(reFindNoCase(regex, "/items/123.json")).toBeGT(0);
});
it("should support custom regex patterns in tokens", function() {
// Taffy supports {tokenName:regex} syntax
var result = makePublic(api, "convertURItoRegex").convertURItoRegex("/users/{id:[0-9]+}");
expect(result.tokens[1]).toBe("id");
// The regex should only match numeric IDs
var regex = result.uriRegex;
expect(reFindNoCase(regex, "/users/123")).toBeGT(0);
});
});
describe("URI regex matching", function() {
it("should match exact static path", function() {
var result = makePublic(api, "convertURItoRegex").convertURItoRegex("/simple");
expect(reFindNoCase(result.uriRegex, "/simple")).toBeGT(0);
});
it("should match path with trailing slash", function() {
var result = makePublic(api, "convertURItoRegex").convertURItoRegex("/simple");
expect(reFindNoCase(result.uriRegex, "/simple/")).toBeGT(0);
});
it("should match path with format extension", function() {
var result = makePublic(api, "convertURItoRegex").convertURItoRegex("/items/{id}");
expect(reFindNoCase(result.uriRegex, "/items/123.json")).toBeGT(0);
expect(reFindNoCase(result.uriRegex, "/items/abc.xml")).toBeGT(0);
});
it("should not match unrelated paths", function() {
var result = makePublic(api, "convertURItoRegex").convertURItoRegex("/users/{id}");
expect(reFindNoCase(result.uriRegex, "/items/123")).toBe(0);
});
it("should handle deeply nested URIs", function() {
var result = makePublic(api, "convertURItoRegex").convertURItoRegex("/api/v1/users/{userId}/orders/{orderId}/items/{itemId}");
expect(arrayLen(result.tokens)).toBe(3);
expect(reFindNoCase(result.uriRegex, "/api/v1/users/1/orders/2/items/3")).toBeGT(0);
});
});
describe("Token value extraction", function() {
it("should extract token values from matched URI", function() {
var uriConfig = makePublic(api, "convertURItoRegex").convertURItoRegex("/users/{id}");
var matchResult = reFindNoSuck(uriConfig.uriRegex, "/users/123");
expect(matchResult).toBeArray();
expect(arrayLen(matchResult)).toBeGTE(1);
expect(matchResult[1]).toBe("123");
});
it("should extract multiple token values", function() {
var uriConfig = makePublic(api, "convertURItoRegex").convertURItoRegex("/users/{userId}/orders/{orderId}");
var matchResult = reFindNoSuck(uriConfig.uriRegex, "/users/42/orders/99");
expect(arrayLen(matchResult)).toBeGTE(2);
expect(matchResult[1]).toBe("42");
expect(matchResult[2]).toBe("99");
});
});
describe("sortURIMatchOrder()", function() {
it("should sort URIs for proper matching order", function() {
var endpoints = {
"^/users/([^\/]+)": { srcUri: "/users/{id}" },
"^/users": { srcUri: "/users" },
"^/users/([^\/]+)/profile": { srcUri: "/users/{id}/profile" }
};
var result = makePublic(api, "sortURIMatchOrder").sortURIMatchOrder(endpoints);
expect(result).toBeArray();
// More specific patterns should be sorted appropriately
expect(arrayLen(result)).toBe(3);
});
});
});
}
/**
* Reimplementation of reFindNoSuck for testing
*/
private function reFindNoSuck(required string pattern, required string data, numeric startPos = 1) {
var local = {};
local.awesome = [];
local.sucky = reFindNoCase(arguments.pattern, arguments.data, arguments.startPos, true);
if (!isArray(local.sucky.len) || arrayLen(local.sucky.len) == 0) {
return [];
}
for (local.i = 1; local.i <= arrayLen(local.sucky.len); local.i++) {
if (local.sucky.len[local.i] > 0 && local.sucky.pos[local.i] > 0) {
local.matchBody = mid(arguments.data, local.sucky.pos[local.i], local.sucky.len[local.i]);
if (local.matchBody != arguments.data) {
arrayAppend(local.awesome, local.matchBody);
}
}
}
return local.awesome;
}
}
@@ -0,0 +1,80 @@
component extends="testbox.system.BaseSpec" {
function run() {
describe("BaseDeserializer", function() {
beforeEach(function() {
variables.deserializer = new core.baseDeserializer();
});
describe("getFromForm()", function() {
it("should parse simple form data", function() {
var body = "name=John&age=30";
var result = deserializer.getFromForm(body);
expect(result.name).toBe("John");
expect(result.age).toBe("30");
});
it("should parse URL-encoded values", function() {
var body = "name=John%20Doe&city=New%20York";
var result = deserializer.getFromForm(body);
expect(result.name).toBe("John Doe");
expect(result.city).toBe("New York");
});
it("should handle empty values", function() {
var body = "name=John&nickname=";
var result = deserializer.getFromForm(body);
expect(result.name).toBe("John");
expect(result.nickname).toBe("");
});
it("should handle multiple values for same key as list", function() {
var body = "color=red&color=blue&color=green";
var result = deserializer.getFromForm(body);
expect(result.color).toInclude("red");
expect(result.color).toInclude("blue");
expect(result.color).toInclude("green");
});
it("should handle special characters when URL encoded", function() {
var body = "query=" & urlEncodedFormat("foo=bar&baz=qux");
var result = deserializer.getFromForm(body);
expect(result.query).toBe("foo=bar&baz=qux");
});
it("should handle plus signs as spaces", function() {
// Note: urlDecode handles + as space
var body = "name=John+Doe";
var result = deserializer.getFromForm(body);
expect(result.name).toBe("John Doe");
});
});
describe("taffy:mime metadata", function() {
it("should have taffy:mime for application/x-www-form-urlencoded", function() {
var metadata = getMetadata(deserializer);
var func = "";
for (var f in metadata.functions) {
if (f.name == "getFromForm") {
func = f;
break;
}
}
expect(func).notToBeEmpty();
var hasMime = structKeyExists(func, "taffy:mime")
|| structKeyExists(func, "taffy_mime");
expect(hasMime).toBeTrue();
});
});
});
}
}
@@ -0,0 +1,325 @@
component extends="testbox.system.BaseSpec" {
function run() {
describe("BaseSerializer", function() {
beforeEach(function() {
variables.serializer = new core.baseSerializer();
});
describe("setData() and getData()", function() {
it("should store and retrieve simple string data", function() {
var data = "Hello World";
serializer.setData(data);
expect(serializer.getData()).toBe(data);
});
it("should store and retrieve struct data", function() {
var data = { name: "Test", value: 123 };
serializer.setData(data);
var result = serializer.getData();
expect(result.name).toBe("Test");
expect(result.value).toBe(123);
});
it("should store and retrieve array data", function() {
var data = [1, 2, 3, 4, 5];
serializer.setData(data);
expect(serializer.getData()).toBeArray();
expect(arrayLen(serializer.getData())).toBe(5);
});
it("should return this for method chaining", function() {
var result = serializer.setData("test");
expect(new tests.helpers.MetadataHelper().extendsClass(result, "baseSerializer")).toBeTrue();
});
it("should set type to textual (1) when setting data", function() {
serializer.setData("test");
expect(serializer.getType()).toBe("textual");
});
});
describe("withStatus()", function() {
it("should set status code with default text", function() {
serializer.withStatus(200);
expect(serializer.getStatus()).toBe(200);
expect(serializer.getStatusText()).toBe("OK");
});
it("should set status code 201 with Created text", function() {
serializer.withStatus(201);
expect(serializer.getStatus()).toBe(201);
expect(serializer.getStatusText()).toBe("Created");
});
it("should set status code 404 with Not Found text", function() {
serializer.withStatus(404);
expect(serializer.getStatus()).toBe(404);
expect(serializer.getStatusText()).toBe("Not Found");
});
it("should set status code 500 with Internal Server Error text", function() {
serializer.withStatus(500);
expect(serializer.getStatus()).toBe(500);
expect(serializer.getStatusText()).toBe("Internal Server Error");
});
it("should allow custom status text", function() {
serializer.withStatus(418, "I'm a teapot - Custom");
expect(serializer.getStatus()).toBe(418);
expect(serializer.getStatusText()).toBe("I'm a teapot - Custom");
});
it("should use lookup table for known status codes", function() {
serializer.withStatus(418);
expect(serializer.getStatusText()).toBe("I'm a teapot");
});
it("should return this for method chaining", function() {
var result = serializer.withStatus(200);
expect(new tests.helpers.MetadataHelper().extendsClass(result, "baseSerializer")).toBeTrue();
});
it("should handle all 2xx status codes", function() {
serializer.withStatus(204);
expect(serializer.getStatusText()).toBe("No Content");
serializer.withStatus(206);
expect(serializer.getStatusText()).toBe("Partial Content");
});
it("should handle 3xx redirect status codes", function() {
serializer.withStatus(301);
expect(serializer.getStatusText()).toBe("Moved Permanently");
serializer.withStatus(302);
expect(serializer.getStatusText()).toBe("Found");
serializer.withStatus(304);
expect(serializer.getStatusText()).toBe("Not Modified");
});
it("should handle 4xx client error status codes", function() {
serializer.withStatus(400);
expect(serializer.getStatusText()).toBe("Bad Request");
serializer.withStatus(401);
expect(serializer.getStatusText()).toBe("Unauthorized");
serializer.withStatus(403);
expect(serializer.getStatusText()).toBe("Forbidden");
serializer.withStatus(405);
expect(serializer.getStatusText()).toBe("Method Not Allowed");
serializer.withStatus(422);
expect(serializer.getStatusText()).toBe("Unprocessable Entity");
serializer.withStatus(429);
expect(serializer.getStatusText()).toBe("Too Many Requests");
});
it("should handle 5xx server error status codes", function() {
serializer.withStatus(501);
expect(serializer.getStatusText()).toBe("Not Implemented");
serializer.withStatus(502);
expect(serializer.getStatusText()).toBe("Bad Gateway");
serializer.withStatus(503);
expect(serializer.getStatusText()).toBe("Service Unavailable");
serializer.withStatus(504);
expect(serializer.getStatusText()).toBe("Gateway Timeout");
});
});
describe("withHeaders()", function() {
it("should set custom headers", function() {
var headers = { "X-Custom-Header": "value1", "X-Another": "value2" };
serializer.withHeaders(headers);
var result = serializer.getHeaders();
expect(result["X-Custom-Header"]).toBe("value1");
expect(result["X-Another"]).toBe("value2");
});
it("should return this for method chaining", function() {
var result = serializer.withHeaders({});
expect(new tests.helpers.MetadataHelper().extendsClass(result, "baseSerializer")).toBeTrue();
});
it("should return empty struct when no headers set", function() {
var result = serializer.getHeaders();
expect(result).toBeStruct();
expect(structIsEmpty(result)).toBeTrue();
});
});
describe("noData()", function() {
it("should return serializer instance when noDataSends204NoContent is false", function() {
serializer.setNoDataSends204NoContent(false);
var result = serializer.noData();
expect(new tests.helpers.MetadataHelper().extendsClass(result, "baseSerializer")).toBeTrue();
expect(result.getStatus()).toBe(200);
});
it("should return 204 status when noDataSends204NoContent is true", function() {
serializer.setNoDataSends204NoContent(true);
var result = serializer.noData();
expect(result.getStatus()).toBe(204);
});
it("should default to false (no 204) without explicit configuration", function() {
var result = serializer.noData();
expect(result.getStatus()).toBe(200);
});
});
describe("noContent()", function() {
it("should set status to 204 No Content", function() {
var result = serializer.noContent();
expect(result.getStatus()).toBe(204);
expect(result.getStatusText()).toBe("No Content");
});
it("should set Content-Type header to text/plain", function() {
var result = serializer.noContent();
var headers = result.getHeaders();
expect(headers["Content-Type"]).toBe("text/plain");
});
});
describe("File streaming methods", function() {
describe("setFileName()", function() {
it("should store filename for streaming", function() {
serializer.setFileName("/path/to/file.pdf");
expect(serializer.getFileName()).toBe("/path/to/file.pdf");
});
it("should set type to filename (2)", function() {
serializer.setFileName("/path/to/file.pdf");
expect(serializer.getType()).toBe("filename");
});
it("should return this for method chaining", function() {
var result = serializer.setFileName("/path/to/file.pdf");
expect(new tests.helpers.MetadataHelper().extendsClass(result, "baseSerializer")).toBeTrue();
});
});
describe("setFileData()", function() {
it("should store binary data for streaming", function() {
var binaryData = toBinary(toBase64("test data"));
serializer.setFileData(binaryData);
expect(serializer.getFileData()).toBe(binaryData);
});
it("should set type to filedata (3)", function() {
serializer.setFileData(toBinary(toBase64("test")));
expect(serializer.getType()).toBe("filedata");
});
});
describe("setImageData()", function() {
it("should store image data for streaming", function() {
var imageData = toBinary(toBase64("fake image data"));
serializer.setImageData(imageData);
expect(serializer.getImageData()).notToBeNull();
});
it("should set type to imagedata (4)", function() {
serializer.setImageData(toBinary(toBase64("test")));
expect(serializer.getType()).toBe("imagedata");
});
it("should convert non-binary data to binary", function() {
serializer.setImageData("test string");
expect(isBinary(serializer.getImageData())).toBeTrue();
});
});
describe("withMime()", function() {
it("should set mime type for file streaming", function() {
serializer.withMime("application/pdf");
expect(serializer.getFileMime()).toBe("application/pdf");
});
it("should return this for method chaining", function() {
var result = serializer.withMime("image/jpeg");
expect(new tests.helpers.MetadataHelper().extendsClass(result, "baseSerializer")).toBeTrue();
});
});
describe("andDelete()", function() {
it("should set delete flag to true", function() {
serializer.andDelete(true);
expect(serializer.getDeleteFile()).toBeTrue();
});
it("should set delete flag to false", function() {
serializer.andDelete(false);
expect(serializer.getDeleteFile()).toBeFalse();
});
it("should return this for method chaining", function() {
var result = serializer.andDelete(true);
expect(new tests.helpers.MetadataHelper().extendsClass(result, "baseSerializer")).toBeTrue();
});
});
});
describe("Method chaining", function() {
it("should support fluent interface for multiple operations", function() {
var result = serializer
.setData({ message: "test" })
.withStatus(201, "Created")
.withHeaders({ "X-Custom": "value" });
expect(result.getData().message).toBe("test");
expect(result.getStatus()).toBe(201);
expect(result.getHeaders()["X-Custom"]).toBe("value");
});
it("should support chaining for file streaming", function() {
var result = serializer
.setFileName("/path/to/file.pdf")
.withMime("application/pdf")
.andDelete(true);
expect(result.getFileName()).toBe("/path/to/file.pdf");
expect(result.getFileMime()).toBe("application/pdf");
expect(result.getDeleteFile()).toBeTrue();
});
});
});
}
}
+161
View File
@@ -0,0 +1,161 @@
component extends="testbox.system.BaseSpec" {
function run() {
describe("Factory", function() {
beforeEach(function() {
variables.factory = new core.factory();
});
describe("init()", function() {
it("should initialize with empty bean cache", function() {
expect(factory.beans).toBeStruct();
expect(structIsEmpty(factory.beans)).toBeTrue();
});
it("should initialize with empty transients cache", function() {
expect(factory.transients).toBeStruct();
expect(structIsEmpty(factory.transients)).toBeTrue();
});
it("should return this for method chaining", function() {
var result = factory.init();
expect(new tests.helpers.MetadataHelper().extendsClass(result, "factory")).toBeTrue();
});
});
describe("beanExists() / containsBean()", function() {
it("should return false for non-existent bean", function() {
expect(factory.beanExists("nonExistent")).toBeFalse();
expect(factory.containsBean("nonExistent")).toBeFalse();
});
it("should return true for cached bean", function() {
factory.beans["testBean"] = { name: "test" };
expect(factory.beanExists("testBean")).toBeTrue();
expect(factory.containsBean("testBean")).toBeTrue();
});
it("containsBean should be alias for beanExists", function() {
factory.beans["aliasTest"] = { name: "test" };
expect(factory.containsBean("aliasTest")).toBe(factory.beanExists("aliasTest"));
});
});
describe("transientExists()", function() {
it("should return false for non-existent transient", function() {
expect(factory.transientExists("nonExistent")).toBeFalse();
});
it("should return true for cached transient", function() {
factory.transients["testTransient"] = "some.component.path";
expect(factory.transientExists("testTransient")).toBeTrue();
});
});
describe("getBean()", function() {
it("should return cached bean if exists", function() {
var testObj = { name: "cached bean" };
factory.beans["testBean"] = testObj;
var result = factory.getBean("testBean");
expect(result.name).toBe("cached bean");
});
it("should throw error for non-existent bean", function() {
expect(function() {
factory.getBean("nonExistent");
}).toThrow("Taffy.Factory.BeanNotFound");
});
});
describe("getBeanList()", function() {
it("should return empty string when no beans", function() {
expect(factory.getBeanList()).toBe("");
});
it("should return bean names as comma-separated list", function() {
factory.beans["bean1"] = {};
factory.beans["bean2"] = {};
var list = factory.getBeanList();
expect(listLen(list)).toBe(2);
expect(listFindNoCase(list, "bean1")).toBeGT(0);
expect(listFindNoCase(list, "bean2")).toBeGT(0);
});
it("should include transients in the list", function() {
factory.beans["bean1"] = {};
factory.transients["transient1"] = "some.path";
var list = factory.getBeanList();
expect(listLen(list)).toBe(2);
expect(listFindNoCase(list, "transient1")).toBeGT(0);
});
});
describe("loadBeansFromPath()", function() {
it("should not throw error for non-existent path", function() {
// loadBeansFromPath should handle non-existent directories gracefully
expect(function() {
factory.loadBeansFromPath("/non/existent/path");
}).notToThrow();
});
it("should clear beans on full reload", function() {
factory.beans["existingBean"] = {};
var taffyRef = {
status: { skippedResources: [] },
beanList: ""
};
factory.loadBeansFromPath(
beanPath = "/non/existent/path",
resourcesPath = "resources",
resourcesBasePath = "",
isFullReload = true,
taffyRef = taffyRef
);
// After reload with non-existent path, beans should be cleared
expect(structIsEmpty(factory.beans)).toBeTrue();
});
});
describe("External bean factory integration", function() {
it("should accept external bean factory in init", function() {
var mockExternalFactory = {
containsBean: function(beanName) { return beanName == "externalBean"; },
getBean: function(beanName) { return { name: "external" }; }
};
factory.init(mockExternalFactory);
expect(structKeyExists(factory, "externalBeanFactory")).toBeTrue();
});
it("should check external factory for beans when configured", function() {
var mockExternalFactory = {
containsBean: function(beanName) { return beanName == "externalBean"; },
getBean: function(beanName) { return { name: "from external", source: "external" }; }
};
factory.init(mockExternalFactory);
// beanExists with includeExternal=true should find external beans
expect(factory.beanExists("externalBean", true, true)).toBeTrue();
});
});
});
}
}
@@ -0,0 +1,149 @@
component extends="testbox.system.BaseSpec" {
function run() {
describe("NativeJsonDeserializer", function() {
beforeEach(function() {
variables.deserializer = new core.nativeJsonDeserializer();
});
describe("inheritance", function() {
it("should extend baseDeserializer", function() {
expect(new tests.helpers.MetadataHelper().extendsClass(deserializer, "baseDeserializer")).toBeTrue();
});
it("should have getFromForm from parent", function() {
expect(deserializer).toHaveKey("getFromForm");
});
});
describe("getFromJson()", function() {
it("should parse simple JSON object", function() {
var body = '{"name":"John","age":30}';
var result = deserializer.getFromJson(body);
expect(result.name).toBe("John");
expect(result.age).toBe(30);
});
it("should parse nested JSON objects", function() {
var body = '{"user":{"name":"John","email":"john@example.com"},"active":true}';
var result = deserializer.getFromJson(body);
expect(result.user.name).toBe("John");
expect(result.user.email).toBe("john@example.com");
expect(result.active).toBeTrue();
});
it("should parse JSON with arrays", function() {
var body = '{"items":[1,2,3],"tags":["a","b","c"]}';
var result = deserializer.getFromJson(body);
expect(result.items).toBeArray();
expect(arrayLen(result.items)).toBe(3);
expect(result.tags[1]).toBe("a");
});
it("should handle boolean values", function() {
var body = '{"active":true,"deleted":false}';
var result = deserializer.getFromJson(body);
expect(result.active).toBeTrue();
expect(result.deleted).toBeFalse();
});
it("should handle null values", function() {
var body = '{"value":null,"other":"test"}';
var result = deserializer.getFromJson(body);
// Note: In Lucee, null values may not preserve the key in the struct
// We verify the deserializer doesn't throw an error and returns a struct
expect(result).toBeStruct();
expect(result.other).toBe("test");
});
it("should handle numeric values", function() {
var body = '{"integer":42,"decimal":3.14,"negative":-100}';
var result = deserializer.getFromJson(body);
expect(result.integer).toBe(42);
expect(result.decimal).toBe(3.14);
expect(result.negative).toBe(-100);
});
it("should handle empty object", function() {
var body = '{}';
var result = deserializer.getFromJson(body);
expect(result).toBeStruct();
expect(structIsEmpty(result)).toBeTrue();
});
it("should wrap non-struct JSON in _body key", function() {
// When JSON is an array at root level, wrap it
var body = '[1,2,3]';
var result = deserializer.getFromJson(body);
expect(structKeyExists(result, "_body")).toBeTrue();
expect(result._body).toBeArray();
});
it("should wrap primitive JSON values in _body key", function() {
var body = '"just a string"';
var result = deserializer.getFromJson(body);
expect(structKeyExists(result, "_body")).toBeTrue();
expect(result._body).toBe("just a string");
});
it("should handle complex nested structures", function() {
var body = '{
"users": [
{"id": 1, "name": "John", "roles": ["admin", "user"]},
{"id": 2, "name": "Jane", "roles": ["user"]}
],
"metadata": {
"total": 2,
"page": 1
}
}';
var result = deserializer.getFromJson(body);
expect(result.users).toBeArray();
expect(arrayLen(result.users)).toBe(2);
expect(result.users[1].roles).toBeArray();
expect(result.metadata.total).toBe(2);
});
it("should handle unicode characters", function() {
var body = '{"message":"Hello \u4e16\u754c"}';
var result = deserializer.getFromJson(body);
expect(structKeyExists(result, "message")).toBeTrue();
});
});
describe("taffy:mime metadata", function() {
it("should have taffy:mime for application/json", function() {
var metadata = getMetadata(deserializer);
var func = "";
for (var f in metadata.functions) {
if (f.name == "getFromJson") {
func = f;
break;
}
}
expect(func).notToBeEmpty();
var hasMime = structKeyExists(func, "taffy:mime")
|| structKeyExists(func, "taffy_mime");
expect(hasMime).toBeTrue();
// Check the actual mime types
var mimeValue = structKeyExists(func, "taffy:mime")
? func["taffy:mime"]
: func["taffy_mime"];
expect(mimeValue).toInclude("application/json");
});
});
});
}
}
@@ -0,0 +1,205 @@
component extends="testbox.system.BaseSpec" {
function beforeAll() {
// Initialize application scope with minimal settings
application._taffy = {
settings: {
serializer: "core.nativeJsonSerializer",
noDataSends204NoContent: false
},
factory: new core.factory(),
compat: {
queryToArray: "missing",
queryToStruct: "missing"
}
};
}
function run() {
describe("NativeJsonSerializer", function() {
beforeEach(function() {
variables.serializer = new core.nativeJsonSerializer();
});
describe("inheritance", function() {
it("should extend baseSerializer", function() {
expect(new tests.helpers.MetadataHelper().extendsClass(serializer, "baseSerializer")).toBeTrue();
});
it("should have all baseSerializer methods", function() {
expect(serializer).toHaveKey("setData");
expect(serializer).toHaveKey("getData");
expect(serializer).toHaveKey("withStatus");
expect(serializer).toHaveKey("withHeaders");
expect(serializer).toHaveKey("noData");
expect(serializer).toHaveKey("noContent");
});
});
describe("getAsJson()", function() {
it("should serialize simple string to JSON", function() {
serializer.setData("Hello World");
var result = serializer.getAsJson();
expect(result).toBe('"Hello World"');
});
it("should serialize struct to JSON", function() {
serializer.setData({ name: "Test", value: 123 });
var result = serializer.getAsJson();
var parsed = deserializeJSON(result);
expect(parsed.name).toBe("Test");
expect(parsed.value).toBe(123);
});
it("should serialize array to JSON", function() {
serializer.setData([1, 2, 3]);
var result = serializer.getAsJson();
var parsed = deserializeJSON(result);
expect(parsed).toBeArray();
expect(arrayLen(parsed)).toBe(3);
});
it("should serialize nested structures to JSON", function() {
serializer.setData({
user: {
name: "John",
roles: ["admin", "user"]
},
active: true
});
var result = serializer.getAsJson();
var parsed = deserializeJSON(result);
expect(parsed.user.name).toBe("John");
expect(parsed.user.roles).toBeArray();
expect(parsed.active).toBeTrue();
});
it("should serialize boolean values correctly", function() {
serializer.setData({ active: true, deleted: false });
var result = serializer.getAsJson();
var parsed = deserializeJSON(result);
expect(parsed.active).toBeTrue();
expect(parsed.deleted).toBeFalse();
});
it("should serialize null values", function() {
serializer.setData({ value: javacast("null", "") });
var result = serializer.getAsJson();
expect(result).toInclude("null");
});
it("should handle empty struct", function() {
serializer.setData({});
var result = serializer.getAsJson();
expect(result).toBe("{}");
});
it("should handle empty array", function() {
serializer.setData([]);
var result = serializer.getAsJson();
expect(result).toBe("[]");
});
it("should handle numeric values", function() {
serializer.setData({
integer: 42,
decimal: 3.14,
negative: -100
});
var result = serializer.getAsJson();
var parsed = deserializeJSON(result);
expect(parsed.integer).toBe(42);
expect(parsed.decimal).toBe(3.14);
expect(parsed.negative).toBe(-100);
});
});
describe("taffy:mime metadata", function() {
it("should have taffy:mime metadata for application/json", function() {
var metadata = getMetadata(serializer);
var getAsJsonFunc = "";
for (var func in metadata.functions) {
if (func.name == "getAsJson") {
getAsJsonFunc = func;
break;
}
}
expect(getAsJsonFunc).notToBeEmpty();
var hasMime = structKeyExists(getAsJsonFunc, "taffy:mime")
|| structKeyExists(getAsJsonFunc, "taffy_mime");
expect(hasMime).toBeTrue();
});
it("should be marked as default serializer", function() {
var metadata = getMetadata(serializer);
var getAsJsonFunc = "";
for (var func in metadata.functions) {
if (func.name == "getAsJson") {
getAsJsonFunc = func;
break;
}
}
var hasDefault = structKeyExists(getAsJsonFunc, "taffy:default")
|| structKeyExists(getAsJsonFunc, "taffy_default");
expect(hasDefault).toBeTrue();
});
});
describe("encode.string helper", function() {
it("should force numeric strings to serialize as strings", function() {
// The forceString function adds chr(2) which is then stripped by getAsJson
// This ensures numeric-looking strings serialize as strings, not numbers
var resource = new core.resource();
var forcedString = resource.forceString("12345");
// The chr(2) prefix forces CF to treat this as a string during serialization
expect(left(forcedString, 1)).toBe(chr(2));
// When serialized via nativeJsonSerializer, the chr(2) is stripped
serializer.setData({ value: forcedString });
var result = serializer.getAsJson();
// The value should be serialized as a string (with quotes), not a number
expect(result).toInclude('"');
});
});
describe("combined operations", function() {
it("should work with setData and status code", function() {
serializer.setData({ error: "Not found" }).withStatus(404);
var result = serializer.getAsJson();
var parsed = deserializeJSON(result);
expect(parsed.error).toBe("Not found");
expect(serializer.getStatus()).toBe(404);
});
it("should work with setData, status, and headers", function() {
serializer
.setData({ created: true })
.withStatus(201)
.withHeaders({ "Location": "/items/123" });
var result = serializer.getAsJson();
var parsed = deserializeJSON(result);
expect(parsed.created).toBeTrue();
expect(serializer.getStatus()).toBe(201);
expect(serializer.getHeaders()["Location"]).toBe("/items/123");
});
});
});
}
}
+132
View File
@@ -0,0 +1,132 @@
component extends="testbox.system.BaseSpec" {
function beforeAll() {
variables.gen = new core.openapi();
makePublic(variables.gen, "normalizePathTemplate");
makePublic(variables.gen, "cfmlTypeToSchema");
makePublic(variables.gen, "buildOperation");
}
function run() {
describe("openapi generator", function() {
describe("normalizePathTemplate", function() {
it("leaves simple paths untouched", function() {
expect(gen.normalizePathTemplate("/users", [])).toBe("/users");
});
it("leaves {name} tokens untouched", function() {
expect(gen.normalizePathTemplate("/users/{id}", ["id"])).toBe("/users/{id}");
});
it("strips {name:regex} down to {name}", function() {
expect(gen.normalizePathTemplate("/users/{id:[0-9]+}", ["id"])).toBe("/users/{id}");
});
it("strips greedy regex like {name:.+}", function() {
expect(gen.normalizePathTemplate("/files/{path:.+}", ["path"])).toBe("/files/{path}");
});
it("handles nested braces in token regexes", function() {
expect(gen.normalizePathTemplate("/a/{b:[a-z]+(?:42){1}}", ["b"])).toBe("/a/{b}");
});
it("rewrites multiple tokens in order", function() {
expect(gen.normalizePathTemplate("/events/{eventId:\d+}/attendees/{personId}", ["eventId", "personId"]))
.toBe("/events/{eventId}/attendees/{personId}");
});
});
describe("cfmlTypeToSchema", function() {
it("maps numeric to number", function() {
expect(gen.cfmlTypeToSchema("numeric")).toBe({ "type": "number" });
});
it("maps boolean to boolean", function() {
expect(gen.cfmlTypeToSchema("boolean")).toBe({ "type": "boolean" });
});
it("maps date to string/date-time", function() {
expect(gen.cfmlTypeToSchema("date")).toBe({ "type": "string", "format": "date-time" });
});
it("maps uuid to string/uuid", function() {
expect(gen.cfmlTypeToSchema("uuid")).toBe({ "type": "string", "format": "uuid" });
});
it("returns empty schema for 'any'", function() {
expect(gen.cfmlTypeToSchema("any")).toBe({});
});
it("returns empty schema for unset type", function() {
expect(gen.cfmlTypeToSchema("")).toBe({});
});
});
describe("buildOperation — path param handling", function() {
// minimal fixtures
var mkEndpoint = function(beanName, srcUri, tokens, methods) {
return { beanName: beanName, srcUri: srcUri, tokens: tokens, methods: methods };
};
var mkFunc = function(name, parameters = []) {
return { name: name, parameters: parameters };
};
it("backfills path tokens not declared as function args", function() {
var endpoint = mkEndpoint("MyResource", "/items/{id}", ["id"], { get: "get" });
var func = mkFunc("get", []); // function declares NO args
var op = gen.buildOperation("get", func, endpoint, {}, ["application/json"]);
expect(op).toHaveKey("parameters");
expect(arrayLen(op.parameters)).toBe(1);
expect(op.parameters[1].name).toBe("id");
expect(op.parameters[1]["in"]).toBe("path");
expect(op.parameters[1].required).toBeTrue();
});
it("uses token's original case when arg casing differs", function() {
var endpoint = mkEndpoint("AttendeesResource", "/attendees/{personId}", ["personId"], { put: "put" });
// simulate CFML metadata that lowercased the arg name
var func = mkFunc("put", [ { name: "personid", type: "numeric", required: true } ]);
var op = gen.buildOperation("put", func, endpoint, {}, ["application/json"]);
expect(op.parameters[1].name).toBe("personId");
expect(op.parameters[1]["in"]).toBe("path");
expect(op.parameters[1].schema).toBe({ "type": "number" });
});
it("puts non-token args for GET into query", function() {
var endpoint = mkEndpoint("ItemsResource", "/items", [], { get: "get" });
var func = mkFunc("get", [ { name: "limit", type: "numeric", required: false } ]);
var op = gen.buildOperation("get", func, endpoint, {}, ["application/json"]);
expect(op.parameters[1].name).toBe("limit");
expect(op.parameters[1]["in"]).toBe("query");
expect(op.parameters[1].required).toBeFalse();
});
it("puts non-token args for POST into requestBody with both json + form", function() {
var endpoint = mkEndpoint("ItemsResource", "/items", [], { post: "post" });
var func = mkFunc("post", [ { name: "name", type: "string", required: true } ]);
var op = gen.buildOperation("post", func, endpoint, {}, ["application/json"]);
expect(op).toHaveKey("requestBody");
expect(op.requestBody.content).toHaveKey("application/json");
expect(op.requestBody.content).toHaveKey("application/x-www-form-urlencoded");
expect(op.requestBody.content["application/json"].schema.properties).toHaveKey("name");
expect(op.requestBody.content["application/json"].schema.required).toBe(["name"]);
});
});
});
}
}
+207
View File
@@ -0,0 +1,207 @@
component extends="testbox.system.BaseSpec" {
function beforeAll() {
// Initialize application scope with minimal settings
application._taffy = {
settings: {
serializer: "core.nativeJsonSerializer",
noDataSends204NoContent: false
},
factory: new core.factory(),
compat: {
queryToArray: "missing",
queryToStruct: "missing"
}
};
// Create sample data fixture
variables.sampleData = new tests.fixtures.SampleData();
}
function run() {
describe("Resource", function() {
beforeEach(function() {
variables.resource = new core.resource();
});
describe("rep() / representationOf()", function() {
it("should return a serializer instance with data", function() {
var result = resource.rep({ message: "test" });
expect(new tests.helpers.MetadataHelper().extendsClass(result, "baseSerializer")).toBeTrue();
expect(result.getData().message).toBe("test");
});
it("rep() should be alias for representationOf()", function() {
var data = { id: 123 };
var rep1 = resource.rep(data);
// Both should return serializer instances with same data
expect(rep1.getData().id).toBe(123);
});
it("should work with simple string data", function() {
var result = resource.rep("Hello World");
expect(result.getData()).toBe("Hello World");
});
it("should work with array data", function() {
var result = resource.rep([1, 2, 3]);
expect(result.getData()).toBeArray();
expect(arrayLen(result.getData())).toBe(3);
});
it("should work with nested struct data", function() {
var data = {
user: { name: "John", age: 30 },
roles: ["admin", "user"]
};
var result = resource.rep(data);
expect(result.getData().user.name).toBe("John");
expect(result.getData().roles).toBeArray();
});
});
describe("encode.string()", function() {
it("should add control character prefix to force string serialization", function() {
// forceString is public (no access modifier), encode.string is just a reference to it
var result = resource.forceString("12345");
expect(left(result, 1)).toBe(chr(2));
});
it("should preserve the original value after prefix", function() {
var result = resource.forceString("12345");
expect(right(result, 5)).toBe("12345");
});
it("should work with numeric-looking strings", function() {
var result = resource.forceString("00123");
expect(len(result)).toBe(6); // chr(2) + "00123"
});
});
describe("qToArray() - internal query to array conversion", function() {
beforeEach(function() {
makePublic(resource, "qToArray");
});
it("should convert query to array of structs", function() {
var q = sampleData.getSampleQuery();
var result = resource.qToArray(q);
expect(result).toBeArray();
expect(arrayLen(result)).toBe(3);
});
it("should preserve column values in structs", function() {
var q = sampleData.getSampleQuery();
var result = resource.qToArray(q);
// Find the row with id=1
var found = false;
for (var row in result) {
if (row.id == 1) {
expect(row.name).toBe("John Doe");
expect(row.email).toBe("john@example.com");
found = true;
break;
}
}
expect(found).toBeTrue();
});
it("should handle empty query", function() {
var q = queryNew("id,name", "integer,varchar");
var result = resource.qToArray(q);
expect(result).toBeArray();
expect(arrayLen(result)).toBe(0);
});
it("should support callback function for row transformation", function() {
var q = sampleData.getSingleRowQuery();
var result = resource.qToArray(q, function(row) {
row.transformed = true;
return row;
});
expect(result[1].transformed).toBeTrue();
});
});
describe("qToStruct() - internal query to struct conversion", function() {
beforeEach(function() {
makePublic(resource, "qToStruct");
});
it("should convert single-row query to struct", function() {
var q = sampleData.getSingleRowQuery();
var result = resource.qToStruct(q);
expect(result).toBeStruct();
expect(result.id).toBe(1);
expect(result.name).toBe("Test User");
});
it("should throw error for multi-row query", function() {
var q = sampleData.getSampleQuery(); // Has 3 rows
expect(function() {
resource.qToStruct(q);
}).toThrow();
});
it("should handle query with all column types", function() {
var q = sampleData.getSingleRowQuery();
var result = resource.qToStruct(q);
expect(structKeyExists(result, "id")).toBeTrue();
expect(structKeyExists(result, "name")).toBeTrue();
expect(structKeyExists(result, "email")).toBeTrue();
});
it("should support callback function for value transformation", function() {
var q = sampleData.getSingleRowQuery();
var result = resource.qToStruct(q, function(colName, value) {
if (colName == "name") {
return uCase(value);
}
return value;
});
expect(result.name).toBe("TEST USER");
});
});
describe("method chaining with rep()", function() {
it("should support chaining with withStatus()", function() {
var result = resource.rep({ created: true }).withStatus(201);
expect(result.getStatus()).toBe(201);
expect(result.getData().created).toBeTrue();
});
it("should support chaining with withHeaders()", function() {
var result = resource.rep({ data: "test" })
.withHeaders({ "X-Custom": "value" });
expect(result.getHeaders()["X-Custom"]).toBe("value");
});
it("should support full fluent chain", function() {
var result = resource.rep({ id: 123 })
.withStatus(201, "Created")
.withHeaders({ "Location": "/items/123" });
expect(result.getData().id).toBe(123);
expect(result.getStatus()).toBe(201);
expect(result.getStatusText()).toBe("Created");
expect(result.getHeaders()["Location"]).toBe("/items/123");
});
});
});
}
}
+27
View File
@@ -0,0 +1,27 @@
component extends="taffy.core.api" {
this.name = "TaffyE2ETestAPI_" & hash(getCurrentTemplatePath());
this.sessionManagement = false;
variables.testApiPath = getDirectoryFromPath(getCurrentTemplatePath());
variables.rootPath = getDirectoryFromPath(left(variables.testApiPath, len(variables.testApiPath) - 1));
variables.rootPath = getDirectoryFromPath(left(variables.rootPath, len(variables.rootPath) - 1));
this.mappings["/taffy"] = variables.rootPath;
this.mappings["/resources"] = variables.testApiPath & "resources";
variables.framework = {
disableDashboard: true,
disabledDashboardRedirect: "",
returnExceptionsAsJson: true
};
function onApplicationStart() {
return super.onApplicationStart();
}
function onRequestStart(targetPath) {
return super.onRequestStart(targetPath);
}
}
+1
View File
@@ -0,0 +1 @@
<!--- This file intentionally left empty. It is required by Taffy to serve as the entry point. --->
@@ -0,0 +1,17 @@
component extends="taffy.core.resource" taffy:uri="/echo/{id}" output="false" {
function get(required string id) output="false" {
return rep({ method: "GET", id: arguments.id });
}
function put(required string id, string name = "") output="false" {
return rep({ method: "PUT", id: arguments.id, name: arguments.name });
}
function delete(required string id) output="false" {
return rep({ method: "DELETE", id: arguments.id })
.withStatus(200)
.withHeaders({ "X-Deleted-Id": arguments.id });
}
}
@@ -0,0 +1,7 @@
component extends="taffy.core.resource" taffy:uri="/echo/{parentId}/child/{childId}" output="false" {
function get(required string parentId, required string childId) output="false" {
return rep({ method: "GET", parentId: arguments.parentId, childId: arguments.childId });
}
}
@@ -0,0 +1,12 @@
component extends="taffy.core.resource" taffy:uri="/echo" output="false" {
function get() output="false" {
return rep({ method: "GET", message: "echo" });
}
function post(string name = "" taffy_minlength="1" taffy_maxlength="255", string value = "" taffy_maxlength="500") output="false" {
return rep({ method: "POST", name: arguments.name, value: arguments.value })
.withStatus(201);
}
}
-29
View File
@@ -1,29 +0,0 @@
<cfcomponent>
<cfset this.name = 'Taffy_testSuite' /> <!---uses the same application name as the api Application.cfc for scope sharing --->
<cfset this.appDirectory = getDirectoryFromPath(getCurrentTemplatePath())>
<cfset this.parentDirectory = reReplace(this.appDirectory, "(.+[/\\])tests[/\\]$", "\1")>
<cfset this.mappings = structNew()>
<cfset this.mappings["/mxunit"] = this.parentDirectory & "testbox/system/compat/">
<cfset this.mappings["/testbox"] = this.parentDirectory & "testbox/">
<cfset this.mappings["/Hoth"] = this.parentDirectory & "Hoth/">
<cfset this.mappings["/di1"] = this.parentDirectory & "di1/">
<cfset this.mappings["/bugLog"] = this.parentDirectory & "BugLogHQ/">
<cfscript>
//remove bugLogHQ Application.cfc so we can override datasource definition
if (fileExists("#this.parentDirectory#/BugLogHQ/Application.cfc")) {
fileDelete("#this.parentDirectory#/BugLogHQ/Application.cfc");
}
</cfscript>
<cffunction name="onRequestStart" returnType="void" access="public" output="false">
<cfif NOT isDefined('application._taffy')>
<cfset local.apiRootURL = getDirectoryFromPath(cgi.script_name) />
<cfset local.apiRootURL = listDeleteAt(local.apiRootURL,listLen(local.apiRootURL,'/'),'/') />
<cfhttp method="GET" url="http://#CGI.SERVER_NAME#:#CGI.SERVER_PORT##local.apiRootURL#/index.cfm" />
</cfif>
</cffunction>
</cfcomponent>
-207
View File
@@ -1,207 +0,0 @@
<cfcomponent hint="automatic test suite runner">
<cfset this.s_results = StructNew()>
<cfset this.s_results.Errors = StructNew()>
<cfset sep = createObject("java","java.lang.System").getProperty("file.separator")>
<cffunction name="run" access="public" hint="runs a directory of tests" returntype="any" output="false">
<cfargument name="directory" required="true" hint="directory of tests to run">
<cfargument name="componentPath" required="false" hint="the component path to put in front of all tests found (i.e. 'com.blah'). If no path is passed, we'll attempt to discover it ourselves" default="">
<cfargument name="recurse" required="false" type="boolean" default="true" hint="whether to recurse down the directory tree">
<cfargument name="excludes" required="false" default="" hint="list of Tests, in cfc notation, to exclude. uses ListContains so it's as greedy as possible. Currently does not support ant-style syntax or whole-directory filtering">
<cfargument name="refreshCache" required="false" default="false" hint="flag to indicate whether or not to refresh the CF cache of CFCs. Maybe needed if any of the tests in the directory have not yet been compiled - to do: prove" />
<cfset var testResult = "">
<cfset var files = "">
<cfset var suite = createObject("component","mxunit.framework.TestSuite")>
<cfset var i = 1>
<cfset arguments.directory = normalizeDirectory(arguments.directory)>
<cfif NOT len(arguments.componentPath)>
<cfset arguments.componentPath = getComponentPath(arguments.directory, arguments.refreshCache)>
</cfif>
<cfset files = getTests(arguments.directory,arguments.componentPath,arguments.recurse,trim(arguments.excludes)) />
<cfloop from="1" to="#ArrayLen(files)#" index="i">
<cftry>
<cfset suite.addAll(files[i])>
<cfcatch>
<cfset this.s_results.Errors[files[i]] = cfcatch>
</cfcatch>
</cftry>
</cfloop>
<cfset testResult = suite.run()>
<cfset testResult.mergeErrorsIntoTestResult(this.s_results.Errors)>
<cfreturn testResult>
</cffunction>
<cffunction name="getTests" access="private" output="false">
<cfargument name="directory" required="true" hint="directory of tests to run">
<cfargument name="componentPath" required="true" hint="the component path to put in front of all tests found (i.e. 'com.blah')">
<cfargument name="recurse" required="false" default="true" type="boolean" hint="whether to recurse down the directory tree">
<cfargument name="excludes" required="false" default="" hint="ant-style syntax for excluding single files or directories">
<cfset var q_tests = getDirectoryQuery(directory,recurse)>
<cfset var a_tests = ArrayNew(1)>
<cfset var testPath = "">
<cfloop query="q_tests">
<cfset testPath = formatTestPath(arguments.directory, q_tests.directory & sep & q_tests.Name,arguments.componentPath)>
<cfif accept(testPath,arguments.excludes)>
<cftry>
<!---
Compile first. This ensures that any new tests NOT found in the server cache get added.
This may actually be quicker than searching the array and then conditionally compiling.
Appears not to have much overhead, but, we should monitor anyway for large test runs.
--->
<cfset createObject("component","#testPath#") />
<cfcatch type="any"><!--- no worries ---></cfcatch>
</cftry>
<cfset ArrayAppend(a_tests,testPath)>
</cfif>
</cfloop>
<cfreturn a_tests>
</cffunction>
<cffunction name="formatTestPath" access="private" output="false">
<cfargument name="directory" required="true" hint="directory of tests to run">
<cfargument name="fullFilePath">
<cfargument name="componentPath" required="true" hint="the component path to put in front of all tests found (i.e. 'com.blah')">
<cfset var formatted = replaceNoCase(fullFilePath,directory,"")>
<cfset formatted = reverse( replace(reverse(formatted),"cfc.","") )>
<cfset formatted = componentPath & "." & formatted>
<cfset formatted = reReplace(formatted, "(\\|/|\.){1,}" ,".","all")>
<cfreturn formatted>
</cffunction>
<cffunction name="getDirectoryQuery" access="private" returntype="query" output="false">
<cfargument name="directory" required="true" hint="directory of tests to run">
<cfargument name="recurse" type="boolean" required="false" default="true" hint="whether to recurse down the directory tree">
<cfset var files = "">
<cfset var runnerUtils = "">
<cfif not DirectoryExists(arguments.directory)>
<cfthrow message="Directory #directory# does not exist">
</cfif>
<cfdirectory action="list" directory="#arguments.directory#" name="files" recurse="#arguments.recurse#" filter="*.cfc">
<cfreturn files>
</cffunction>
<cffunction name="getCatastrophicErrors" output="false">
<cfreturn this.s_results.Errors>
</cffunction>
<cffunction name="accept" hint="rudimentary initial implementation." access="private" output="false">
<cfargument name="test" required="true">
<cfargument name="excludes" required="true">
<cfset var testName = ListLast(arguments.test,".")>
<cfset var thisExclude = "">
<cfif NOT reFindNoCase("^test",testName) AND NOT reFindNoCase("test$",testName)>
<cfreturn false>
</cfif>
<cfif not len(excludes)>
<cfreturn true>
</cfif>
<cfset arguments.test = ListChangeDelims(arguments.test,"/",".")>
<cfloop list="#excludes#" index="thisExclude" delimiters=",">
<cfset thisExclude = replace(thisExclude,"\","/")>
<cfif reFindNoCase("\b#thisExclude#\b",arguments.test)>
<cfreturn false>
</cfif>
</cfloop>
<cfreturn true>
</cffunction>
<cffunction name="normalizeDirectory" hint="makes sure every directory has ending slashes" access="private" output="false">
<cfargument name="Directory" type="string" required="true"/>
<cfset var dir = createObject("java","java.io.File")>
<cfif not DirectoryExists(arguments.directory)>
<cfset arguments.directory = expandPath(arguments.directory) />
<cfif not DirectoryExists(arguments.directory)>
<cfthrow message="Directory #directory# does not exist">
</cfif>
</cfif>
<cfset dir.init(arguments.directory)>
<cfset arguments.directory = dir.getCanonicalPath()>
<cfset arguments.directory = replaceList(arguments.directory,"/,\","#sep#,#sep#")>
<cfset arguments.directory = arguments.directory & sep>
<cfreturn arguments.directory>
</cffunction>
<cffunction name="getComponentPath" access="remote" returntype="string" hint="Given a directory path, returns the corresponding CFC package according to CFMX" output="false">
<cfargument name="path" type="string" required="true" />
<cfargument name="refreshCache" type="boolean" required="false" default="false" />
<cfscript>
var explorer = createObject("component", "CFIDE.componentutils.cfcexplorer");
var target = explorer.normalizePath(arguments.path);
var cfcs = explorer.getcfcs(refreshCache); //true == refresh cache
var cfc = "";
var i = 1 ;
var package = "";
var cfcpath = "";
for(i = 1; i lte arraylen(cfcs); i = i+1){
cfc = cfcs[i];
//Assumes that CF always stores path info with fwd slash
//and strip last element to get path.
cfcpath = ListDeleteAt(cfc.path, listlen(cfc.path,"/"), "/");
//Array of structs. Doesn't seem possible to do binary search
if(cfcpath eq target){
package = cfc.package;
break;
}
}
return package;
</cfscript>
</cffunction>
<cffunction name="getComponentPathx" access="remote" returntype="string" hint="Given a directory path, returns the corresponding CFC package" output="false">
<cfargument name="path" type="string" required="true" />
<cfargument name="refreshCache" type="boolean" required="false" default="false" />
<cfset var files = '' />
<cfset var package = '' />
<cfdirectory action="list" type="file" name="files" directory="#arguments.path#" filter="*.cfc" />
<cfif files.recordCount>
<cfset package = replace(normalizePath(files.directory), '/', '.', 'all') />
</cfif>
<cfreturn package />
</cffunction>
<cffunction name="normalizePath" access="public" returntype="string" output="false">
<cfargument name="path" type="string" required="true" />
<cfif not len(arguments.path)>
<cfreturn arguments.path />
</cfif>
<cfset arguments.path = replace(arguments.path, '\', '/', 'all') />
<cfif right(arguments.path, 1) eq '/'>
<cfset arguments.path = left(arguments.path, len(arguments.path) - 1) />
</cfif>
<cfreturn arguments.path />
</cffunction>
</cfcomponent>
-92
View File
@@ -1,92 +0,0 @@
<cfcomponent output="true">
<cfsetting enablecfoutputonly="true" showdebugoutput="false">
<cffunction name="run" access="remote" output="true" returntype="void" hint="Generates and prints HTML, JUnit style XML, or XML data based on a directory of tests.">
<cfargument name="type" type="string" hint="Specifies the type to run: TestCase(testcase) or Directory Path (dir)" required="true" />
<cfargument name="value" type="string" hint="The value for the type: com.foo.MyTestCase or C:/my/tests/" required="true" />
<cfargument name="packagename" type="string" hint="The package name for JUnitReportTask" required="false" default="mxunit.testresults" />
<cfargument name="outputformat" type="string" hint="Valid Values: HTML,XML, or JUNITXML" required="false" default="junitxml" />
<cfargument name="recurse" required="false" default="true" hint="whether to recurse down the directory tree">
<cfargument name="excludes" required="false" default="" hint="List of files to exclude if type is DIR">
<cfargument name="componentPath" required="false" hint="performance improver: pass the cfc-notation. See DirectoryTestSuite for details." default="">
<cfset var suite = createObject("component","mxunit.framework.TestSuite").TestSuite()/>
<cfset var results = createObject("component","mxunit.framework.TestResult").TestResult()/>
<cfscript>
if(arguments.type is "testcase"){
suite = createObject("component","mxunit.framework.TestSuite").TestSuite();
suite.addAll(arguments.value);
results = suite.run();
}
if(arguments.type is "dir"){
//To Do: add args for recursion, includes, and excludes
if(not isBoolean(arguments.recurse)){
arguments.recurse = false;
}
results = createObject("component","DirectoryTestSuite").run(directory=arguments.value, componentPath=arguments.componentPath, recurse=arguments.recurse, excludes=arguments.excludes);
}
//package name for JUnit reports
results.setPackage(arguments.packagename);
</cfscript>
<!--- Read by Ant client and used to print summary to stdout --->
<cfcookie name="mxunit_summary" value="#results.testRuns#,#results.testErrors#,#results.testFailures#,#results.totalExecutionTime#" />
<!--- write the cookie first --->
<cfset printResults(arguments.outputformat,results) />
<cfreturn />
</cffunction>
<cffunction name="printResults" access="private">
<cfargument name="type">
<cfargument name="results">
<cfoutput>
<cfswitch expression="#type#">
<cfcase value="html">
#trim(arguments.results.getHTMLResults())#
</cfcase>
<cfcase value="xml">
#trim(arguments.results.getXMLResults())#
</cfcase>
<cfcase value="junitxml">
#trim(arguments.results.getJUnitXMLResults())#
</cfcase>
<cfdefaultcase>#trim(arguments.results.getJUnitXMLResults())#</cfdefaultcase>
</cfswitch>
</cfoutput>
</cffunction>
<cffunction name="runFast" access="remote" output="true" returntype="void" hint="Generates and prints HTML, JUnit style XML, or XML data based on a directory of tests.">
<cfargument name="type" type="string" hint="Specifies the type to run: TestCase(testcase) or Directory Path (dir)" required="true" />
<cfargument name="value" type="string" hint="The value for the type: com.foo.MyTestCase or C:/my/tests/" required="true" />
<cfargument name="packagename" type="string" hint="The package name for JUnitReportTask" required="false" default="mxunit.testresults" />
<cfargument name="outputformat" type="string" hint="Valid Values: HTML,XML, or JUNITXML" required="false" default="junitxml" />
<cfargument name="recurse" required="false" default="true" hint="whether to recurse down the directory tree">
<cfargument name="excludes" required="false" default="" hint="List of files to exclude if type is DIR">
<cfargument name="componentPath" required="false" hint="performance improver: pass the cfc-notation. See DirectoryTestSuite for details." default="">
<cfset var suite = createObject("component","mxunit.framework.TestSuite").TestSuite()/>
<cfset var results = createObject("component","mxunit.framework.TestResult").TestResult()/>
<cfscript>
if(arguments.type is "testcase"){
suite = createObject("component","mxunit.framework.TestSuite").TestSuite();
suite.addAll(arguments.value);
results = suite.run();
}
if(arguments.type is "dir"){
//To Do: add args for recursion, includes, and excludes
if(not isBoolean(arguments.recurse)){
arguments.recurse = false;
}
results = createObject("component","DirectoryTestSuite").run(directory=arguments.value, componentPath=arguments.componentPath, recurse=arguments.recurse, excludes=arguments.excludes);
}
//package name for JUnit reports
results.setPackage(arguments.packagename);
</cfscript>
<!--- Read by Ant client and used to print summary to stdout --->
<cfcookie name="mxunit_summary" value="#results.testRuns#,#results.testErrors#,#results.testFailures#,#results.totalExecutionTime#" />
<!--- write the cookie first
<cfset printResults(arguments.outputformat,results) /> --->
<cfreturn />
</cffunction>
</cfcomponent>
-1
View File
@@ -1 +0,0 @@
<cfcomponent extends="mxunit.framework.RemoteFacade"></cfcomponent>
-22
View File
@@ -1,22 +0,0 @@
<cfcomponent extends="base">
<cffunction name="setup">
<cfset reloadFramework()>
</cffunction>
<cfscript>
function beforeTests(){
variables.taffy = createObject("component","taffy.tests.Application");
makePublic(variables.taffy, "getBeanFactory");
variables.factory = variables.taffy.getBeanFactory();
variables.factory.loadBeansFromPath( expandPath('/taffy/tests/resourcesConflict'), 'taffy.tests.resourcesConflict', expandPath('/taffy/tests/resourcesConflict'), true, variables.taffy);
}
function test_conflicting_URIs_get_skipped() {
assertTrue(checkIfOneSkippedRessourceContainsExpectedException("errorCode", "taffy.resources.DuplicateUriPattern"), "Conflicting URIs not showing in errors");
}
</cfscript>
</cfcomponent>
-645
View File
@@ -1,645 +0,0 @@
<cfcomponent extends="base">
<cffunction name="setup">
<cfset reloadFramework()>
</cffunction>
<cfscript>
function beforeTests(){
variables.taffy = createObject("component","taffy.tests.Application");
makePublic(variables.taffy, "getBeanFactory");
variables.factory = variables.taffy.getBeanFactory();
variables.factory.loadBeansFromPath( expandPath('/taffy/tests/resources'), 'taffy.tests.resources', expandPath('/taffy/tests/resources'), true );
}
function test_properly_notifies_unimplemented_mimes(){
makePublic(variables.taffy, "mimeSupported");
// debug(variables.taffy);
// debug(application);
assertFalse(taffy.mimeSupported("DoesNotExist"), "When given a mime type that should not exist, Taffy reported that it did.");
}
function test_properly_notifies_implemented_mimes(){
makePublic(variables.taffy, "mimeSupported");
makePublic(variables.taffy, "inspectMimeTypes");
variables.taffy.inspectMimeTypes('taffy.core.nativeJsonSerializer', variables.taffy.getBeanFactory());
assertTrue(taffy.mimeSupported("json"));
assertTrue(taffy.mimeSupported("text/json"));
assertTrue(taffy.mimeSupported("application/json"));
}
function test_returns_access_control_expose_headers_header(){
local.result = apiCall("get", "/echo/foo.json", "");
assertTrue(structKeyExists(local.result.responseHeader, "Access-Control-Expose-Headers"));
debug(local.result.responseHeader["Access-Control-Expose-Headers"]);
assertTrue(findNoCase("Etag", local.result.responseHeader["Access-Control-Expose-Headers"]));
}
function test_returns_etag_header(){
//both requests should yield the same etag header
local.result = apiCall("get", "/echo/foo.json", "");
local.result2 = apiCall("get", "/echo/foo.json", "");
assertTrue(structKeyExists(local.result.responseHeader, "Etag"));
assertTrue(structKeyExists(local.result2.responseHeader, "Etag"));
assertEquals(local.result2.responseHeader.etag, local.result.responseHeader.etag);
}
function test_returns_304_when_not_modified(){
local.result = apiCall("get", "/echo/foo.json", "");
assertTrue(structKeyExists(local.result.responseHeader, "Etag"));
local.h = {};
local.h['if-none-match'] = local.result.responseHeader.etag;
local.result = apiCall("get", "/echo/foo.json", "", local.h);
debug(local.result);
assertEquals(304, val(local.result.responseHeader.status_code));
}
function test_json_result_is_json(){
local.result = apiCall ("get","/echo/2.json","bar=foo");
// debug(local.result);
assertTrue(isJson(local.result.fileContent), "Expected JSON content back but was not able to identify it as such.");
}
function test_custom_status_is_returned(){
local.result = apiCall("get", "/echo/1.json?foo=bar", "");
// debug(local.result);
// debug(application);
assertEquals(999, local.result.responseHeader.status_code, "Expected status code 999 but got something else.");
}
function test_custom_headers_work(){
local.result = apiCall("get", "/echo/-1.json", "");
// debug(local.result);
assertTrue(structKeyExists(local.result.responseHeader, "x-dude"), "Expected response header `x-dude` but it was not included.");
}
function test_global_headers_work(){
local.result = apiCall("get", "/echo/1.json", "");
// debug(local.result);
assertTrue(structKeyExists(local.result.responseHeader, "x-foo-globalheader"), "Expected response header `x-foo-globalheader` but it was not included.");
}
function test_deserializer_inspection_finds_all_content_types(){
makePublic(variables.taffy, "getSupportedContentTypes");
local.result = variables.taffy.getSupportedContentTypes("taffy.core.baseDeserializer");
// debug(local.result);
assertTrue(structKeyExists(local.result, "application/x-www-form-urlencoded"));
local.result = variables.taffy.getSupportedContentTypes("taffy.core.nativeJsonDeserializer");
// debug(local.result);
assertTrue(structKeyExists(local.result, "application/json"));
assertTrue(structKeyExists(local.result, "text/json"));
assertTrue(structKeyExists(local.result, "application/x-www-form-urlencoded"));
}
function test_deserializer_support_detection_works(){
makePublic(variables.taffy, "contentTypeIsSupported");
// debug(application._taffy.contentTypes);
assertTrue(variables.taffy.contentTypeIsSupported("application/json"));
assertTrue(variables.taffy.contentTypeIsSupported("application/json;v=1"));
assertFalse(variables.taffy.contentTypeIsSupported("application/does-not-exist"));
}
function test_uri_regexes_are_correct(){
makePublic(variables.taffy, "convertURItoRegex");
local.result = taffy.convertURItoRegex("/a/{abc}/b");
// debug(local.result);
assertEquals( "^/a/([^\/]+)/b((?:\.)[^\.\?\/]+)?\/?$", local.result["uriregex"], "Resulted regex did not match expected. (assert 1)");
assertEquals( 1, arrayLen(local.result["tokens"]), "assert 2" );
assertEquals( "abc", local.result["tokens"][1], "assert 3" );
local.result2 = taffy.convertURItoRegex("/a/{abc}");
// debug(local.result2);
assertEquals( "^/a/(?:(?:([^\/\.]+)(?:\.)([a-za-z0-9]+))\/?|([^\/\.]+))((?:\.)[^\.\?\/]+)?\/?$", local.result2["uriregex"], "Resulted regex did not match expected.");
assertEquals( 1, arrayLen(local.result2["tokens"]) );
assertEquals( "abc", local.result2["tokens"][1] );
//custom regexes for tokens
local.result3 = taffy.convertURItoRegex("/a/{b:[a-z]+(?:42){1}}");
// debug(local.result3);
assertEquals( "^/a/([a-z]+(?:42){1})((?:\.)[^\.\?\/]+)?\/?$", local.result3["uriregex"], "Resulted regex did not match expected. (assert 7)");
assertEquals( 1, arrayLen(local.result3["tokens"]), "assert 8" );
assertEquals( "b", local.result3["tokens"][1], "assert 9" );
local.result4 = taffy.convertURItoRegex("/a/{b:[0-4]{1,7}(?:aaa){1}}/c/{d:\d+}");
// debug(local.result4);
assertEquals( "^/a/([0-4]{1,7}(?:aaa){1})/c/(\d+)((?:\.)[^\.\?\/]+)?\/?$", local.result4["uriregex"], "Resulted regex did not match expected. (assert 10)");
assertEquals( 2, arrayLen(local.result4["tokens"]), "assert 11" );
assertEquals( "b", local.result4["tokens"][1], "assert 12" );
assertEquals( "d", local.result4["tokens"][2], "assert 13" );
}
function test_uri_matching_works_with_extension(){
makePublic(variables.taffy, "matchURI");
local.result = variables.taffy.matchURI("/echo/3.json");
// debug(local.result);
assertEquals('^/echo/(?:(?:([^\/\.]+)(?:\.)([a-za-z0-9]+))\/?|([^\/\.]+))((?:\.)[^\.\?\/]+)?\/?$', local.result);
}
function test_uri_matching_works_without_extension(){
makePublic(variables.taffy, "matchURI");
local.result = variables.taffy.matchURI("/echo/3");
// debug(local.result);
assertEquals('^/echo/(?:(?:([^\/\.]+)(?:\.)([a-za-z0-9]+))\/?|([^\/\.]+))((?:\.)[^\.\?\/]+)?\/?$', local.result);
}
function test_uri_matching_works_with_trailing_slash_with_extension(){
makePublic(variables.taffy, "matchURI");
local.result = variables.taffy.matchURI("/echo/3.json/");
// debug(local.result);
assertEquals('^/echo/(?:(?:([^\/\.]+)(?:\.)([a-za-z0-9]+))\/?|([^\/\.]+))((?:\.)[^\.\?\/]+)?\/?$', local.result);
}
function test_uri_matching_works_with_trailing_slash_without_extension(){
makePublic(variables.taffy, "matchURI");
local.result = variables.taffy.matchURI("/echo/3/");
// debug(local.result);
assertEquals('^/echo/(?:(?:([^\/\.]+)(?:\.)([a-za-z0-9]+))\/?|([^\/\.]+))((?:\.)[^\.\?\/]+)?\/?$', local.result);
}
function test_uri_matching_is_sorted_so_static_URIs_take_priority_over_tokens(){
makePublic(variables.taffy, "matchURI");
local.result = variables.taffy.matchURI("/echo/3");
// debug(local.result);
assertEquals('^/echo/(?:(?:([^\/\.]+)(?:\.)([a-za-z0-9]+))\/?|([^\/\.]+))((?:\.)[^\.\?\/]+)?\/?$', local.result);
local.result = variables.taffy.matchURI("/echo/towel");
// debug(local.result);
assertEquals('^/echo/towel((?:\.)[^\.\?\/]+)?\/?$', local.result);
}
function test_request_parsing_works(){
makePublic(variables.taffy,"buildRequestArguments");
local.result = variables.taffy.buildRequestArguments(
regex = '/echo/([^\/\.]+)$',
tokenNamesArray = listToArray("id"),
uri = '/echo/16',
queryString = 'foo=bar&bar=foo',
headers = structNew()
);
// debug(local.result);
assertTrue(structKeyExists(local.result, "foo") && local.result.foo == "bar", "Missing or incorrect value for key `foo`.");
assertTrue(structKeyExists(local.result, "bar") && local.result.bar == "foo", "Missing or incorrect value for key `bar`.");
assertTrue(structKeyExists(local.result, "id") && local.result.id == 16, "Missing or incorrect value for key `id`.");
}
function test_properly_decodes_urlEncoded_put_request_body(){
local.result = apiCall("put", "/echo/99.json", "foo=bar&check=mate");
// debug(local.result);
if (!isJson(local.result.fileContent)){
// debug(local.result.fileContent);
fail("Result was not JSON");
return local.result.fileContent;
}
local.result = deserializeJSON(local.result.fileContent);
assertTrue(structKeyExists(local.result, "foo") && local.result.foo == "bar", "Missing or incorrect value for key `foo`.");
assertTrue(structKeyExists(local.result, "check") && local.result.check == "mate", "Missing or incorrect value for key `check`.");
}
function test_properly_decodes_json_put_request_body(){
local.result = apiCall("put", "/echo/99.json", '{"foo":"bar"}');
// debug(local.result);
if (!isJson(local.result.fileContent)){
fail("Result was not JSON");
return;
}
local.result = deserializeJSON(local.result.fileContent);
// debug(local.result);
assertTrue(structKeyExists(local.result, "foo") && local.result.foo == "bar", "Missing or incorrect value for key `foo`.");
}
function test_properly_decodes_json_post_request_body(){
local.result = apiCall("post", "/echo/99.json", '{"foo":"bar"}');
// debug(local.result);
if (!isJson(local.result.fileContent)){
fail("Result was not JSON");
return;
}
local.result = deserializeJSON(local.result.fileContent);
// debug(local.result);
assertTrue(structKeyExists(local.result, "foo") && local.result.foo == "bar", "Missing or incorrect value for key `foo`.");
}
function test_returns_error_when_requested_mime_not_supported(){
local.h = structNew();
local.h['Accept'] = "application/NOPE";
local.result = apiCall ("get","/echo/2","foo=bar", local.h);
// debug(local.result);
assertEquals(400, local.result.responseHeader.status_code);
assertEquals("Requested mime type is not supported (application/NOPE)", local.result.responseHeader.explanation);
}
function test_extension_takes_precedence_over_accept_header(){
local.headers = structNew();
local.headers["Accept"] = "text/xml";
local.result = apiCall("get","/echo/2.json","foo=bar",local.headers);
// debug(local.result);
assertEquals(999, local.result.responseHeader.status_code);
assertTrue(isJson(local.result.fileContent));
}
function test_allows_regex_as_final_url_value(){
makePublic(variables.taffy, "buildRequestArguments");
local.headers = structNew();
local.headers.Accept = "application/json";
local.tokenArray = arrayNew(1);
arrayAppend(local.tokenArray, "id");
local.result = variables.taffy.buildRequestArguments(
"^/echo/([a-zA-Z0-9_\-\.\+]+@[a-zA-Z0-9_\-\.]+\.?[a-zA-Z]+)((?:\.)[^\.\?]+)?$",
local.tokenArray,
"/echo/foo@bar.com",
"",
local.headers
);
// debug(local.result);
assertTrue(local.result._taffy_mime eq "json", "Did not detect desired return format correctly.");
//full integration test for a@b.c.json
local.result = apiCall("get", "/echo_regex/12345.json", "", {});
// debug(local.result);
assert(isJson(local.result.fileContent), "response was not json");
local.response = deserializeJSON(local.result.fileContent);
assertEquals("12345", local.response.id);
//full integration test for a@b.c (no .json, but with headers)
local.result = apiCall("get", "/echo_regex/12345", "", local.headers);
// debug(local.result);
assert(isJson(local.result.fileContent), "response was not json");
local.response = deserializeJSON(local.result.fileContent);
assertEquals("12345", local.response.id);
}
function test_returns_405_for_unimplemented_verbs(){
local.result = apiCall("delete", "/echo/2.json", "foo=bar");
// debug(local.result);
assertEquals(405, local.result.responseHeader.status_code);
}
function test_test_onTaffyRequest_allow(){
local.result = apiCall("get","/echo/12.json","refuse=false");
// debug(local.result);
assertEquals(999,local.result.responseHeader.status_code);
}
function test_onTaffyRequest_deny(){
local.result = apiCall("get","/echo/12.json","refuse=true");
// debug(local.result);
assertEquals(405,local.result.responseHeader.status_code);
}
function test_getCacheKey_customBehavior() {
local.result = variables.taffy.getCacheKey(
"EchoMember",
{ "foo": "bar" },
"/echo/12.json"
);
assertEquals("echomember_foo", local.result);
}
function test_getCacheKey_defaultBehavior() {
local.args = {
cfc: "EchoMember",
requestArguments: { "default": true },
matchedURI: "/echo/12.json"
};
local.result = variables.taffy.getCacheKey(argumentCollection = local.args);
// ACF and Lucee generate hash code differently
assertEquals("/echo/12.json_#local.args.requestArguments.hashCode()#", local.result);
}
function test_external_file_request_passes_through(){
local.result = getUrl('http://#CGI.SERVER_NAME#:#CGI.SERVER_PORT##replace(cgi.script_name, "/tests/tests/run.cfm", "/tests/someFolder/someOtherFile.cfm")#');
debug(local.result);
assertTrue(findNoCase('woot', local.result.fileContent), "Was not able to get the DMZ file.");
}
function test_tunnel_PUT_through_POST(){
var local = {};
local.headers["X-HTTP-Method-Override"] = "PUT";
local.result = apiCall("post","/echo/tunnel/12.json","", local.headers);
// debug(local.result);
assertEquals(200,local.result.responseHeader.status_code);
local.deserializedContent = deserializeJSON( local.result.fileContent );
// debug( local.deserializedContent );
assertEquals("put", local.deserializedContent.actualMethod);
}
function test_tunnel_DELETE_through_POST(){
var local = {};
local.headers["X-HTTP-Method-Override"] = "DELETE";
local.result = apiCall("post","/echo/tunnel/12.json","", local.headers);
// debug(local.result);
assertEquals(200,local.result.responseHeader.status_code);
local.deserializedContent = deserializeJSON( local.result.fileContent );
// debug( local.deserializedContent );
assertEquals("delete", local.deserializedContent.actualMethod);
}
function test_put_body_is_mime_content(){
var local = {};
local.result = apiCall(
"put",
"/echo/12.json",
'{"foo":"The quick brown fox jumped over the lazy dog."}'
);
// debug(local.result);
assertEquals(200,local.result.responseHeader.status_code);
local.deserializedContent = deserializeJSON( local.result.fileContent );
// debug( local.deserializedContent );
// The service response should contain only the ID parameter, and not anything parsed from the body
assertEquals("foo,id,password,username", listSort(structKeylist(local.deserializedContent), "textnocase"));
assertEquals(12, local.deserializedContent["id"]);
assertEquals("The quick brown fox jumped over the lazy dog.", local.deserializedContent["foo"]);
}
function test_put_body_is_url_encoded_params(){
var local = {};
local.result = apiCall(
"put",
"/echo/12.json",
"foo=yankee&bar=hotel&baz=foxtrot"
);
// debug(local.result);
assertEquals(200,local.result.responseHeader.status_code);
local.deserializedContent = deserializeJSON( local.result.fileContent );
// debug( local.deserializedContent );
// The service response should contain the ID parameter and all parsed form fields from the body
local.sortedKeys = listSort(structKeylist(local.deserializedContent), "textnocase");
//because apparently railo includes fieldnames when ACF doesn't...
assertTrue("bar,baz,foo,id,password,username" eq local.sortedKeys or "bar,baz,fieldnames,foo,id,password,username" eq local.sortedKeys);
assertEquals(12, local.deserializedContent["id"]);
assertEquals("yankee", local.deserializedContent["foo"]);
assertEquals("hotel", local.deserializedContent["bar"]);
assertEquals("foxtrot", local.deserializedContent["baz"]);
}
function test_get_queryString_keys_without_values_returns_empty_string() {
makePublic(variables.taffy, "buildRequestArguments");
var returnedArguments = variables.taffy.buildRequestArguments(
regex = "^/testResource/$",
tokenNamesArray = [],
uri = "/testResource/",
queryString = "keyOne=valueOne&keyTwo=&keyThree=valueThree",
headers = {}
);
assertEquals("", returnedArguments["keyTwo"]);
}
function test_returns_allow_header_for_405(){
local.result = apiCall("delete","/echo/12.json","");
// debug(local.result);
assertEquals(405,local.result.responseHeader.status_code);
assertTrue(structKeyExists(local.result.responseHeader, "allow"),"Expected ALLOW header, but couldn't find it");
}
function test_returns_allow_header_for_get_200(){
local.result = apiCall("get","/echo/tunnel/12.json","");
// debug(local.result);
assertEquals(200,local.result.responseHeader.status_code);
assertTrue(structKeyExists(local.result.responseHeader, "allow"),"Expected ALLOW header, but couldn't find it");
}
function test_returns_allow_header_for_post_201(){
local.result = apiCall("post","/echo/tunnel/12.json","");
// debug(local.result);
assertEquals(201,local.result.responseHeader.status_code);
assertTrue(structKeyExists(local.result.responseHeader, "allow"),"Expected ALLOW header, but couldn't find it");
}
function test_returns_allow_header_for_put_200(){
local.result = apiCall("put","/echo/tunnel/12.json","");
// debug(local.result);
assertEquals(200,local.result.responseHeader.status_code);
assertTrue(structKeyExists(local.result.responseHeader, "allow"),"Expected ALLOW header, but couldn't find it");
}
function test_returns_allow_header_for_delete_200(){
local.result = apiCall("delete","/echo/tunnel/12.json","");
// debug(local.result);
assertEquals(200,local.result.responseHeader.status_code);
assertTrue(structKeyExists(local.result.responseHeader, "allow"),"Expected ALLOW header, but couldn't find it");
}
function test_can_pass_data_from_onTaffyRequest_to_resource(){
local.result = apiCall("get", "/echo/dude.json", "hulk=smash");
// debug(local.result);
local.body = deserializeJSON(local.result.fileContent);
assertTrue(structKeyExists(local.body, "dataFromOTR"));
assertTrue(local.body.dataFromOTR eq "who let the hulk out?!");
}
function test_reload_on_every_request_setting_works(){
application._taffy.settings.reloadOnEveryRequest = false;
local.result = apiCall("get", "/echo/dude.json", "");
// debug(local.result);
assertFalse(structKeyExists(local.result.responseheader, "X-TAFFY-RELOADED"), "Expected reload header to be missing, but it was sent.");
application._taffy.settings.reloadOnEveryRequest = true;
local.result2 = apiCall("get", "/echo/dude.json", "");
// debug(local.result2);
assertTrue(structKeyExists(local.result2.responseheader, "X-TAFFY-RELOADED"), "Expected reload header to be sent, but it was missing.");
}
function test_returns_error_when_resource_throws_exception(){
local.result = apiCall("get", "/throwException.json", "");
// debug(local.result);
assertEquals(500, local.result.responseHeader.status_code);
assertTrue( isJson( local.result.fileContent ), "Response body was not json" );
}
function test_basic_auth_credentials_found(){
local.result = apiCall("get", "/basicauth.json", "", {}, "Towel:42");
// debug(local.result);
assertTrue(isJson(local.result.fileContent));
local.data = deserializeJSON(local.result.fileContent);
assertTrue(structKeyExists(local.data, "username"));
assertEquals("Towel", local.data.username);
assertTrue(structKeyExists(local.data, "password"));
assertEquals("42", local.data.password);
}
function test_getHostname_returns_not_blank(){
local.hostname = variables.taffy.getHostname();
// debug(local.hostname);
assertNotEquals( "", local.hostname );
}
function test_envConfig_is_applied(){
// debug( application._taffy.settings.reloadPassword );
assertEquals( "dontpanic", application._taffy.settings.reloadPassword );
}
function test_use_endpointURLParam_in_GET(){
local.result = apiCall('get','?#application._taffy.settings.endpointURLParam#=/echo/2606.json','');
// debug(local.result);
assertEquals(999,val(local.result.statusCode));
}
function test_use_endpointURLParam_in_POST(){
local.result = apiCall('post','?#application._taffy.settings.endpointURLParam#=/echo/2606.json','bar=foo');
// debug(local.result);
assertEquals(200,val(local.result.statusCode));
}
function test_use_endpointURLParam_in_PUT(){
local.result = apiCall('put','?#application._taffy.settings.endpointURLParam#=/echo/2606.json','bar=foo');
// debug(local.result);
assertEquals(200,val(local.result.statusCode));
}
function test_use_endpointURLParam_in_DELETE(){
local.result = apiCall('delete','?#application._taffy.settings.endpointURLParam#=/echo/tunnel/2606.json','');
// debug(local.result);
assertEquals(200,val(local.result.statusCode));
}
function test_allows_dashboard_when_enabled(){
var restore = application._taffy.settings.disableDashboard;
application._taffy.settings.disableDashboard = false;
local.result = apiCall("get", "/", "");
// debug(local.result);
assertEquals(200, val(local.result.statusCode));
application._taffy.settings.disableDashboard = restore;
}
function test_returns_403_at_root_when_dashboard_disabled_with_no_redirect(){
var restore = application._taffy.settings.disableDashboard;
application._taffy.settings.disableDashboard = true;
local.result = apiCall("get", "/", "");
// debug(local.result);
assertEquals(403, val(local.result.statusCode));
application._taffy.settings.disableDashboard = restore;
}
function test_returns_302_at_root_when_dashboard_disabled_with_redirect(){
var restore1 = application._taffy.settings.disableDashboard;
var restore2 = application._taffy.settings.disabledDashboardRedirect;
application._taffy.settings.disableDashboard = true;
application._taffy.settings.disabledDashboardRedirect = 'https://google.com';
local.result = apiCall("get", "/", "");
// debug(local.result);
assertEquals(302, val(local.result.statusCode));
assertTrue(structKeyExists(local.result.responseHEader, "location"));
assertEquals(application._taffy.settings.disabledDashboardRedirect, local.result.responseHeader.location);
application._taffy.settings.disableDashboard = restore1;
application._taffy.settings.disabledDashboardRedirect = restore2;
}
function test_properly_returns_wrapped_jsonp(){
application._taffy.settings.jsonp = "callback";
local.result = apiCall("get", "/echo/dude.json?callback=zomg", '');
// debug(local.result);
assertEquals('zomg(', left(local.result.fileContent, 5), "Does not begin with call to jsonp callback");
assertEquals(");", right(local.result.fileContent, 2), "Does not end with `);`");
}
function test_properly_handles_arbitrary_cors_headers(){
//see: https://github.com/atuttle/Taffy/issues/144
application._taffy.settings.allowCrossDomain = true;
local.h = { "Access-Control-Request-Headers" = "goat, pigeon, man-bear-pig", "Origin":"http://#cgi.server_name#/"};
local.result = apiCall("get", "/echo/dude.json", "", local.h);
//debug(local.result);
assertTrue(local.result.responseHeader["Access-Control-Allow-Headers"] contains "goat");
assertTrue(local.result.responseHeader["Access-Control-Allow-Headers"] contains "pigeon");
assertTrue(local.result.responseHeader["Access-Control-Allow-Headers"] contains "man-bear-pig");
}
function test_properly_handles_arbitrary_cors_headers_on_error(){
//see: https://github.com/atuttle/Taffy/issues/159
application._taffy.settings.allowCrossDomain = true;
local.h = { "Access-Control-Request-Headers" = "goat, pigeon, man-bear-pig", "Origin":"http://#cgi.server_name#/"};
local.result = apiCall("get", "/throwException.json", "", local.h);
// debug(local.result);
assertTrue(structKeyExists(local.result.responseHeader, "Access-Control-Allow-Origin"));
assertTrue(structKeyExists(local.result.responseHeader, "Access-Control-Allow-Methods"));
assertTrue(structKeyExists(local.result.responseHeader, "Access-Control-Allow-Headers"));
assertTrue(local.result.responseHeader["Access-Control-Allow-Headers"] contains "goat");
assertTrue(local.result.responseHeader["Access-Control-Allow-Headers"] contains "pigeon");
assertTrue(local.result.responseHeader["Access-Control-Allow-Headers"] contains "man-bear-pig");
}
function test_non_struct_json_body_sent_to_resource_as_underscore_body(){
//see: https://github.com/atuttle/Taffy/issues/169
local.result = apiCall("post", "/echo/5", "[1,2,3]");
// debug(local.result);
local.response = deserializeJSON(local.result.fileContent);
assertTrue(structKeyExists(local.response, "_body"));
assertTrue(isArray(local.response._body));
assertTrue(arrayLen(local.response._body) == 3);
}
function test_comma_delim_list_of_uris_for_alias(){
//works with /echo_alias/{ID}
local.result = apiCall("get", "/echo_alias/4", "");
//debug(local.result);
assertEquals(200, val(local.result.statusCode));
assertEquals(serializeJSON({ID="4"}), local.result.fileContent);
//works with /echo_alias
local.result = apiCall("get", "/echo_alias", "");
// debug(local.result);
assertEquals(200, val(local.result.statusCode));
assertEquals(serializeJSON({ID="0"}), local.result.fileContent);
//works with /echo_alias/ (trailing slash)
local.result = apiCall("get", "/echo_alias/", "");
// debug(local.result);
assertEquals(200, val(local.result.statusCode));
assertEquals(serializeJSON({ID="0"}), local.result.fileContent);
//works with /echo_alias?ID=x
local.result = apiCall("get", "/echo_alias", "ID=2");
// debug(local.result);
assertEquals(200, val(local.result.statusCode));
assertEquals(serializeJSON({ID="2"}), local.result.fileContent);
}
</cfscript>
<cffunction name="test_can_upload_a_file">
<cfset var local = structNew() />
<cfset local.apiRootURL = getDirectoryFromPath(cgi.script_name) />
<cfset local.apiRootURL = listDeleteAt(local.apiRootURL,listLen(local.apiRootURL,'/'),'/') />
<cfhttp
url="http://#cgi.server_name#:#cgi.server_port##local.apiRootURL#/index.cfm/upload"
method="post"
result="local.uploadResult">
<cfhttpparam type="file" name="img" file="#expandPath('/taffy/tests/tests/upload.png')#" />
</cfhttp>
<!--- <cfset debug(local.uploadResult) /> --->
<cfset assertTrue(local.uploadResult.statusCode eq "200 OK", "Did not return status 200") />
</cffunction>
<cffunction name="test_throws_exception_when_ressource_uri_doesnt_begin_with_forward_slash">
<cfset assertTrue(checkIfOneSkippedRessourceContainsExpectedException("detail", "The URI (uriWithoutForwardSlash) for `uriDoesntBeginWithForwardSlash` should begin with a forward slash."), "Uri without forward slash not showing in errors")>
</cffunction>
<cffunction name="test_throws_exception_when_alias_ressource_uri_doesnt_begin_with_forward_slash">
<cfset assertTrue(checkIfOneSkippedRessourceContainsExpectedException("detail", "The URI (uriAliasWithoutFowardSlash) for `uriAliasDoesntBeginWithForwardSlash` should begin with a forward slash."), "Uri alias without forward slash not showing in errors")>
</cffunction>
</cfcomponent>
-94
View File
@@ -1,94 +0,0 @@
component extends="base" {
function setup(){
reloadFramework();
}
function teardown() {
reloadFramework();
}
function beforeTests(){
}
function test_loads_directory_named_resources() {
// Setup external factory to read resource directory. Ignore resources that were designed to cause errors.
local.config = {
exclude = ["uriDoesnt","uriAliasDoesnt","conflict"]
};
local.beanFactory = createObject("component", "di1.ioc").init(["/taffy/tests/resources"], local.config);
// Setup Taffy with new bean factory to use and call 'onApplicationStart' to properly initialize.
local.taffy = createObject("component","taffy.tests.Application");
makePublic(local.taffy, "getExternalBeanFactory");
makePublic(local.taffy, "getBeanListFromExternalFactory");
injectMethod(local.taffy, this, "_getVariables", "getVariables");
local.vars = local.taffy.getVariables();
local.vars.framework.beanFactory = local.beanFactory;
local.taffy.onApplicationStart();
// Grab the resource list and compare.
local.resourceList = local.taffy.getBeanListFromExternalFactory(local.taffy.getExternalBeanFactory());
structDelete(local.vars.framework, "beanFactory");
local.taffy.onApplicationStart();
debug("Available resources from external factory: '" & replace(local.resourceList, ",", ", ", "all") & "'");
assert(listLen(local.resourceList) gt 0, "No resources were loaded from the external factory.");
}
function test_loads_explicit_named_resources() {
// Setup external factory to read resource directory. Ignore resources that were designed to cause errors.
local.config = {
exclude = ["uriDoesnt","uriAliasDoesnt","conflict"],
omitDirectoryAliases = true
};
local.beanFactory = createObject("component", "di1.ioc").init(["/taffy/tests/resources"], local.config);
// Setup Taffy with new bean factory to use and call 'onApplicationStart' to properly initialize.
local.taffy = createObject("component","taffy.tests.Application");
makePublic(local.taffy, "getExternalBeanFactory");
makePublic(local.taffy, "getBeanListFromExternalFactory");
injectMethod(local.taffy, this, "_getVariables", "getVariables");
local.vars = local.taffy.getVariables();
local.vars.framework.beanFactory = local.beanFactory;
local.taffy.onApplicationStart();
// Grab the resource list and compare.
debug(local.beanFactory.getBeanInfo());
local.resourceList = local.taffy.getBeanListFromExternalFactory(local.taffy.getExternalBeanFactory());
structDelete(local.vars.framework, "beanFactory");
local.taffy.onApplicationStart();
debug("Available resources from external factory: '" & replace(local.resourceList, ",", ", ", "all") & "'");
//THIS TEST CURRENTLY FAILING DUE TO https://github.com/atuttle/Taffy/issues/276
//assert(listLen(local.resourceList) gt 0, "No resources were loaded from the external factory.");
}
private function _getVariables(){
return variables;
}
}
-101
View File
@@ -1,101 +0,0 @@
<cfcomponent extends="base">
<cfscript>
function setup() {
reloadFramework();
}
function beforeTests(){
request["_testsRunning"] = true;
variables.taffy = createObject("component","taffy.tests.Application");
makePublic(variables.taffy, "getBeanFactory");
variables.factory = variables.taffy.getBeanFactory();
//debug(variables.factory);
variables.factory.loadBeansFromPath( expandPath('/taffy/tests/resources'), 'taffy.tests.resources', expandPath('/taffy/tests/resources'), true, variables.taffy);
}
function test_throws_on_getBean_not_exists(){
var local = {};
local.nonExistentBean = "does_not_exist";
try{
local.result = variables.factory.getBean(local.nonExistentBean);
fail("Expected 'Bean Not Found' exception to be thrown, but none was.");
} catch (Taffy.Factory.BeanNotFound e) {
//debug(e);
assertTrue(findNoCase('not found', e.message) gt 0, "TaffyFactory exception message did not contain the words 'not found'.");
}
}
function test_autowires_properties_in_beans(){
variables.factory.loadBeansFromPath( expandPath('/taffy/tests/resources'), 'taffy.tests.resources', expandPath('/taffy/tests/resources'), true );
local.bean = variables.factory.getBean( 'EchoMember' );
// debug(local.bean);
assertTrue( structKeyExists(local.bean, "dependency1") );
assertFalse( isSimpleValue(local.bean.dependency1) );
}
function test_autowires_setters_in_beans(){
variables.factory.loadBeansFromPath( expandPath('/taffy/tests/resources'), 'taffy.tests.resources', expandPath('/taffy/tests/resources'), true );
local.bean = variables.factory.getBean( 'EchoRegexMember' );
// debug(local.bean);
assertTrue( structKeyExists(local.bean, "dependency2") );
assertFalse( isSimpleValue(local.bean.dependency2) );
}
function test_autowires_properties_in_transients(){
variables.factory.loadBeansFromPath( expandPath('/taffy/tests/resources'), 'taffy.tests.resources', expandPath('/taffy/tests/resources'), true );
local.bean = variables.factory.getBean( 'CustomJsonSerializer' );
// debug(local.bean);
assertTrue( structKeyExists(local.bean, "dependency1") );
assertFalse( isSimpleValue(local.bean.dependency1) );
}
function test_autowires_setters_in_transients(){
variables.factory.loadBeansFromPath( expandPath('/taffy/tests/resources'), 'taffy.tests.resources', expandPath('/taffy/tests/resources'), true );
local.bean = variables.factory.getBean( 'CustomJsonSerializer' );
// debug(local.bean);
assertTrue( structKeyExists(local.bean, "dependency2") );
assertFalse( isSimpleValue(local.bean.dependency2) );
}
function test_skips_resources_with_errors(){
variables.factory.loadBeansFromPath( expandPath('/taffy/tests/resourcesError'), 'taffy.tests.resourcesError', expandPath('/taffy/tests/resourcesError'), true );
// debug(variables.taffy.status);
assertTrue(structKeyExists(variables.taffy.status, "skippedResources"));
}
function test_lists_skipped_resources(){
variables.factory.loadBeansFromPath( expandPath('/taffy/tests/resourcesError'), 'taffy.tests.resourcesError', expandPath('/taffy/tests/resourcesError'), true, variables.taffy);
debug(variables.taffy.status);
assertTrue(structKeyExists(variables.taffy.status, "skippedResources"));
assertTrue( arrayLen(variables.taffy.status.skippedResources) gt 0 );
}
function test_clears_skipped_resources_on_reload(){
variables.factory.loadBeansFromPath( expandPath('/taffy/tests/resourcesError'), 'taffy.tests.resourcesError', expandPath('/taffy/tests/resourcesError'), true, variables.taffy );
// debug(variables.taffy.status);
assertTrue(structKeyExists(variables.taffy.status, "skippedResources"));
assertTrue( arrayLen(variables.taffy.status.skippedResources) gt 0 );
variables.factory.loadBeansFromPath( expandPath('/taffy/tests/resources'), 'taffy.tests.resources', expandPath('/taffy/tests/resources'), true, variables.taffy );
// debug(variables.taffy.status);
assertTrue(structKeyExists(variables.taffy.status, "skippedResources"));
assertTrue( ArrayLen(variables.taffy.status.skippedResources) eq 0, "Expected skipped resources array to be empty but it wasn't" );
}
function test_treats_CRCs_as_transients(){
var local = {};
//this resource+method explicitly sets response status of 999
local.result = apiCall("get", "/echo/2.json", "foo=bar");
// debug(local.result);
assertEquals(999, local.result.responseHeader.status_code);
//this resource+method usees the default response status => 200=passing test, 999=failing test
local.result = apiCall("get", "/echo/tunnel/2.json", "foo=bar");
// debug(local.result);
assertEquals(200, local.result.responseHeader.status_code);
}
</cfscript>
</cfcomponent>
-63
View File
@@ -1,63 +0,0 @@
<cfcomponent extends="base">
<cfscript>
function test_hoth(){
var mockHoth = getMockBox().createMock(className="Hoth.HothTracker", callLogging=true);
var hothAdapter = '';
var fakeError = {};
var callLog = {};
//setup the behaviors we're expecting the adapter to run
//mockHoth.track('{struct}');
mockHoth.$('track', true);
//create hoth adapter to test
hothAdapter = createObject("component", "taffy.bonus.LogToHoth").init(
"Taffy.examples.api_Hoth.resources.HothConfig",
mockHoth
);
//fake error
fakeError.message = "This is a test error";
fakeError.detail = "Rubber Baby Buggy Bumper";
hothAdapter.saveLog(fakeError);
callLog = mockHoth.$callLog();
debug(callLog);
assertTrue(structKeyExists(callLog, "track"), "should call track method");
assertTrue(isStruct(callLog.track[1][1]), "args should be a struct");
assertEquals(callLog.track[1][1].message, fakeError.message);
}
function test_BugLogHQ(){
var blhqSettings = { bugLogListener = "http://#cgi.server_name#:#cgi.server_port#/tests/BugLogHQ/listeners/bugLogListenerREST.cfm" };
var mockBLHQ = new bugLog.client.bugLogService(argumentCollection=blhqSettings);
var blhqAdapter = '';
var fakeError = {};
fakeError.message = "This is a test error";
fakeError.detail = "Rubber Baby Buggy Bumper";
if (structKeyExists(server, "coldfusion") && structKeyExists(server.coldfusion, "productversion")) {
if (val(listFirst(server.coldfusion.productversion)) < 11) {
//datasource definition will only work on CF11+
return;
}
}
mockBLHQ.init(argumentCollection=blhqSettings);
blhqAdapter = createObject("component", "taffy.bonus.LogToBugLogHQ").init(
config=blhqSettings,
tracker=mockBLHQ
);
//fake error
fakeError.message = "This is a test error";
fakeError.detail = "Rubber Baby Buggy Bumper";
blhqAdapter.saveLog(fakeError);
}
</cfscript>
</cfcomponent>
@@ -1,40 +0,0 @@
<cfcomponent extends="base">
<cfscript>
function setup(){
variables.serializer = createObject("component", "taffy.core.baseSerializer");
}
function test_setData_getData(){
variables.serializer.setData(10);
assertEquals(10, variables.serializer.getData());
}
function test_noData_returns_empty_rep_obj(){
local.result = variables.serializer.noData();
local.meta = getMetaData(local.result);
// debug(local.meta);
// debug(local.result.getData());
assertEquals('taffy.core.baseSerializer', local.meta.fullname);
assertEquals("", local.result.getData());
}
function test_withStatus_getStatus(){
variables.serializer.withStatus(42);
assertEquals(42, variables.serializer.getStatus());
}
function test_withStatus_getStatusText(){
variables.serializer.withStatus(404, "Not Found");
assertEquals("Not Found", variables.serializer.getStatusText());
variables.serializer.withStatus(418);
assertEquals("I'm a teapot", variables.serializer.getStatusText());
}
function test_withHeaders_getHeaders(){
local.h = {};
local.h['x-dude'] = 'dude!';
variables.serializer.withHeaders(local.h);
assertEquals(true, structKeyExists(variables.serializer.getHeaders(), "x-dude"));
}
</cfscript>
</cfcomponent>
-50
View File
@@ -1,50 +0,0 @@
component extends="base" {
function beforeTests(){
variables.taffy = createObject("component","taffy.tests.Application");
variables.resource = createObject("component", "taffy.core.resource");
}
function test_representationOf_returns_repClass(){
application._taffy.settings.serializer = "taffy.core.nativeJsonSerializer";
makePublic(variables.resource, "representationOf");
local.result = variables.resource.representationOf(10);
local.meta = getMetadata(local.result);
// debug(local.meta);
assertEquals(true, eventuallyInherits(local.meta, 'taffy.core.baseSerializer'));
}
function test_queryToArray_respects_column_case(){
makePublic(variables.resource, "queryToArray");
local.before = QueryNew("Foo,Bar");
queryAddRow(local.before);
querySetCell(local.before, "Foo", 42, 1);
querySetCell(local.before, "Bar", "fubar", 1);
// debug(local.before);
local.after = variables.resource.queryToArray(local.before);
// debug(local.after);
local.keyList = structKeyList(local.after[1]);
local.serialized = serializeJSON(local.after);
// debug(local.serialized);
assertTrue( (local.keyList eq "Foo,Bar" or local.keyList eq "Bar,Foo"), 'column name case is not as expected');
assertTrue( (local.serialized eq '[{"Foo":42,"Bar":"fubar"}]') or (local.serialized eq '[{"Bar":"fubar","Foo":42}]') );
}
/* recursive method used to check entire inheritance tree to find that a certain parent class exists somewhere within it */
private boolean function eventuallyInherits(required struct md, required string class) {
if (structKeyExists(md, "fullname") && md.fullname eq class) {
return true;
} else {
if (structKeyExists(md, "extends")) {
return eventuallyInherits(md.extends, class);
} else {
return false;
}
}
}
}
-83
View File
@@ -1,83 +0,0 @@
<cfcomponent extends="mxunit.framework.TestCase">
<cffunction name="apiCall" access="private" output="false">
<cfargument name="method" type="string"/>
<cfargument name="uri" type="string"/>
<cfargument name="query" type="string"/>
<cfargument name="headers" type="struct" default="#structNew()#" />
<cfargument name="basicauth" type="string" default="" />
<cfset var local = structNew() />
<cfset local.apiRootURL = getDirectoryFromPath(cgi.script_name) />
<cfset local.apiRootURL = listDeleteAt(local.apiRootURL,listLen(local.apiRootURL,'/'),'/') />
<cfset local.args = {} />
<cfset local.args.method = arguments.method />
<cfset local.args.redirect = false />
<cfset local.args.result = "local.result" />
<cfset local.args.charset = "utf-8" />
<cfif len(arguments.basicauth)>
<cfset local.args.username = listFirst(arguments.basicauth, ":") />
<cfset local.args.password = listRest(arguments.basicAuth, ":") />
</cfif>
<cfset local.args.url = "http://#CGI.SERVER_NAME#:#CGI.SERVER_PORT##local.apiRootURL#/index.cfm#arguments.uri#" />
<cfif lcase(arguments.method) eq "put" or lcase(arguments.method) eq "post">
<cfhttp attributeCollection="#local.args#">
<cfif isJson(query)>
<cfhttpparam type="header" name="Content-Type" value="text/json" />
<cfelse>
<cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded" />
</cfif>
<cfhttpparam type="body" value="#arguments.query#" />
<!--- Add arbitrary headers to request --->
<cfloop item="local.header" collection="#arguments.headers#">
<cfhttpparam type="header" name="#local.header#" value="#arguments.headers[local.header]#" />
</cfloop>
</cfhttp>
<cfelse>
<cfif find('?',local.args.url)>
<cfset local.args.url = '#local.args.url##arguments.query#' />
<cfelse>
<cfset local.args.url = '#local.args.url#?#arguments.query#' />
</cfif>
<cfhttp attributeCollection="#local.args#">
<!--- Add arbitrary headers to request --->
<cfloop item="local.header" collection="#arguments.headers#">
<cfhttpparam type="header" name="#local.header#" value="#arguments.headers[local.header]#" />
</cfloop>
</cfhttp>
</cfif>
<cfreturn local.result />
</cffunction>
<cffunction name="getUrl" access="private" output="false">
<cfargument name="url" type="string" required="true" />
<cfhttp method="get" url="#arguments.url#" result="local.result" />
<cfreturn local.result />
</cffunction>
<cffunction name="checkIfOneSkippedRessourceContainsExpectedException" access="private" output="false" returntype="boolean">
<cfargument name="exceptionAttributeName" type="string" required="true" />
<cfargument name="expectedValue" type="string" required="true" />
<cfset var skippedRessource = "">
<cfset var expectedExceptionAttributeValueFound = 0>
<cfloop array="#application._taffy.status.skippedResources#" index="skippedRessource">
<cfset expectedExceptionAttributeValueFound = structKeyExists(skippedRessource.exception, arguments.exceptionAttributeName) and skippedRessource.exception[arguments.exceptionAttributeName] is arguments.expectedValue>
<cfif expectedExceptionAttributeValueFound>
<cfbreak>
</cfif>
</cfloop>
<cfreturn expectedExceptionAttributeValueFound>
</cffunction>
<cffunction name="reloadFramework" access="private">
<cfset local.apiRootURL = getDirectoryFromPath(cgi.script_name) />
<cfset local.apiRootURL = listDeleteAt(local.apiRootURL,listLen(local.apiRootURL,'/'),'/') />
<cfhttp method="GET" url="http://#CGI.SERVER_NAME#:#CGI.SERVER_PORT##local.apiRootURL#/index.cfm?#application._taffy.settings.reloadkey#=#application._taffy.settings.reloadPassword#" />
</cffunction>
</cfcomponent>
-10
View File
@@ -1,10 +0,0 @@
<cfsetting requesttimeout="600" />
<cfinvoke component="mxunit.runner.DirectoryTestSuite"
method="run"
directory="#expandPath('/Taffy/tests/tests')#"
componentPath="Taffy.tests.tests"
recurse="true"
returnvariable="results" />
<cfoutput> #results.getResultsOutput('extjs')# </cfoutput>
-19
View File
@@ -1,19 +0,0 @@
<cfsetting showdebugoutput="false" />
<cfscript>
testsDir = expandPath('/Taffy/tests/tests');
componentPath = 'Taffy.tests.tests';
suite = createObject('mxunit.framework.TestSuite');
files = createObject('mxunit.runner.DirectoryTestSuite').getTests(directory=testsDir, componentPath=componentPath);
for (i = 1; i <= arrayLen(files); i++){
suite.addAll(files[i]);
}
tests = suite.suites();
for (t in tests){
tests[t] = tests[t].methods;
}
</cfscript>
<cfcontent reset="true" type="application/json; charset=UTF-8" /><cfoutput>#serializeJson(tests)#</cfoutput><cfabort/>
-46
View File
@@ -1,46 +0,0 @@
<cfparam name="url.reporter" default="simple" type="variablename">
<cfset plainText = url.reporter IS "text" OR url.reporter IS "mintext">
<cfsetting requesttimeout="150" enablecfoutputonly="#plainText#">
<cfif plainText>
<cfcontent type="text/plain">
</cfif>
<strong>Test Environment</strong>:
<cfif server.coldfusion.productname contains "ColdFusion">
<cfoutput>Adobe ColdFusion #server.coldfusion.productversion#</cfoutput>
<cfelseif structKeyExists(server, "lucee")>
<cfoutput>Lucee #server.lucee.version#</cfoutput>
<cfelseif structKeyExists(server, "railo")>
<cfoutput>Railo #server.railo.version#</cfoutput>
<cfelse>
<cfoutput>#server.coldfusion.productname#</cfoutput>
</cfif>
<cfoutput> on #server.os.name# #server.os.version#</cfoutput>
<cfset system = createObject("java", "java.lang.System")>
<cfoutput> running Java #system.getProperty("java.version")#
</cfoutput>
<cfset bundles=[]>
<cfdirectory action="list" directory="#getDirectoryFromPath(getCurrentTemplatePath())#" name="files">
<cfloop query="files">
<cfif left(getFileFromPath(files.name), 4) IS "Test">
<cfset arrayAppend(bundles, "Taffy.tests.tests." & replace(getFileFromPath(files.name), ".cfc", ""))>
</cfif>
</cfloop>
<cfset r = new testbox.system.TestBox(reporter=url.reporter, bundles=bundles)>
<cfsavecontent variable="resultOutput"><cfoutput>#r.run()#</cfoutput></cfsavecontent>
<cfoutput>#trim(resultOutput)#</cfoutput>
<cfset resultObject = r.getResult()>
<cfset errors = resultObject.getTotalFail() + resultObject.getTotalError()>
<cfif errors GT 0>
<cfheader statuscode="500" statustext="Tests Failed">
</cfif>
<cfif NOT plainText>
<cfoutput>
<a href="run.cfm?ts=#GetTickCount()#">ReRun</a>
</cfoutput>
</cfif>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB