234 attempt to fix js error Uncaught SyntaxError: Unexpected identifier 'hidden'
This commit is contained in:
@@ -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");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user