I'm trying to debug something. For some reason th...
# cfml-general
t
I'm trying to debug something. For some reason this:
getThreatLevelName(-1)
is throwing
The THREATLEVEL argument passed to the getThreatLevelName function is not of type numeric.
So I'm trying to figure out what it's really getting passed, because it is obviously not -1, which absolutely is numeric. I've tried changing the function arg to
any
and then dumping it. But it gives me a stack overflow, like it's some sort of recursive object. So I'm wondering if there's a better way to find out what type this mystery value is.
p
This appears to work correctly on Lucee and CF2023
m
I can't remember the specifics, but I feel like I've gotten the same message when passing a -1 as a numeric.
t
my code is in a custom TestBox matcher... I'm starting to think that it might be passing an expectation object.
p
Why would be expecting numeric vs whatever object?
Also if you change it to
any
and it is still failing, sounds like there is a bigger issue going on
t
Okay, so what I have is some custom matchers in a CFC.
Copy code
component {

	boolean function toHaveRecords(required expectation, args={}) {
		try { 
			var records = new Parser().parseDataLayer(expectation.actual);
			var level = args.keyExists("1") && IsNumeric(args[1]) ? args[1] : 0;
			expectation.message = args.keyExists("message") ? args.message : "The alert has [#records.len()#] record(s) at [#getThreatLevelName(level)#].";
			if( expectation.isNot ) {
				return records.len() == 0;
			}
			return records.len() > 0;
		}
		catch( any exception ) {
			fail(exception.message);
		}
	}

	private string function getThreatLevelName(required numeric threatLevel) {
		switch( threatLevel ) {
			case 1:
				return "Severe (red)";
			case 2:
				return "High (orange)";
			case 3:
				return "Elevated (yellow)";
			case 4:
				return "Guarded (blue)";
			case 5:
				return "Low (green)";
			default:
				return "Unknown";
		}
	}

    ...
}
so the matcher
toHaveRecords
is defined. And it calls
getThreatLevelName
to get a human -readable failure message.
The first problem I ran into was that TestBox couldn't see the private function at all. So I made it public.
And then it complained that
level
wasn't numeric. So I dumped it, and found it to be -1. So then I hardcoded -1 into that line. And still get the same error. That's the point at which I posted here.
p
one thing to try when cfdumping an object that leads to an overflow/timeout is the 'top' attribute... you might want to set it to 1 and try the cfdump again to see if you can tell whats being passed
t
And now i'm thinking that TestBox might be doing something like calling any of the public functions with arguments of
(expectation, args)
, so that my "threatlevel" argument is treated as an expectation, and the -1 isn't getting used.
w
seems like if you just change the datatype of the threatLevel arg to string you should get past the not numeric check at least, which may reveal the next piece
☝🏻 1
t
then it just complains that it's not a simple value either, which wasn't especially useful. @Paul Gallo thanks for the top trick. I didn't realize that was a thing. That confirmed my suspicion that it's an expectation object.
p
Whats your stack error, I am not following what the problem is...this seems like a super simple function/tool that should be simple to resolve
What is complaining that it is not a "simple value"
t
switch
is complaining that
threatLevel
is not a simple value. Because it is, in fact, an expectation object. TestBox is doing something funky, which is not obvious from within my code.
p
yea your
case 1
etc needs to be
case "1"
That switch aka the
getThreatLevelName()
function needs to just receive a simple number...
w
maybe it's as simple as using named values:
[#getThreatLevelName(threatLevel=level)#
p
Dump right after
var level
abort and see what it is returning (aka a number or not).
t
I did that. it dumps -1, which is a number. I dumped the arguments scope from within the getThreatLevelFunction (with top so it does't go all recursive on me), and it's this:
w
did you try the named parameter call ?
t
So my code is calling getThreatLevelName(-1), but ortus's codei is calling that as
getThreatLevelName(expectation, {1: -1}
w
clearly testbox is injecting that additional expectation argument
t
right. and no, i haven't yet tried with a named argument.
w
i should like to think it should append it positionally so it's at the 'end', but it doesn't do that it seems
t
yeah. the defined prototype for a custom expectation is
boolean function(expectation, args={})
So I think it's assuming that all public functions are custom expectations, and calling them as such.
which is annoying for what I'm trying to do....
p
Yea that sounds like what is occurring
t
yeah... and then when I get past that, it complains that it can't convert my returned string to a boolean. I apparently need to come up with something completely different.
p
Does changing to the proper manner of calling your argument like so
switch( arguments.threatlevel )
have any affect?
t
kinda. I ended up doing this
Copy code
public string function getThreatLevelName(required any expectation, args={}) {
		var threatLevel = args.keyExists("threatLevel") && IsNumeric(args.threatLevel) ? args.threatLevel : 0;
		switch( threatLevel ) {
			case 1:
				return "Severe (red)";
			case 2:
				return "High (orange)";
			case 3:
				return "Elevated (yellow)";
			case 4:
				return "Guarded (blue)";
			case 5:
				return "Low (green)";
			default:
				return "Unknown";
		}
	}
but then it complains that
"Unknown" is not a boolean
.
I just can't do this this way...
p
Where is boolean coming into play?
Also, nothing about your
toHaveRecords()
returns anything other than a number...
unless its in the catch
t
the framework is expecting the function to be
boolean function myCustomMatcher(required any expectation, struct args={})
. and then calling the function as if it matches the prototype, which is does not.
so then all these weird bad things are happening.
p
just do
any
on the function?
t
it's not going to do any good. TestBox is still going to call it as if it returns a boolean. and it still won't.
I'm going to try moving it to a different class and see if I can call it as I do with Parser.
🤷🏻‍♂️ 1
p
Well good luck 🙂
w
untested and rough, but what about something like this:
Copy code
component {

	public boolean function toHaveRecords(required expectation, args={}) {
		try { 
			var records = new Parser().parseDataLayer(expectation.actual);
			var level = args.keyExists("1") && IsNumeric(args[1]) ? args[1] : 0;
			expectation.message = args.keyExists("message") ? args.message : "The alert has [#records.len()#] record(s) at [#$_getThreatLevelName(level)#].";
			if( expectation.isNot ) {
				return records.len() == 0;
			}
			return records.len() > 0;
		}
		catch( any exception ) {
			fail(exception.message);
		}
	}

	private string function getThreatLevelName(required numeric threatLevel) {
		switch( threatLevel ) {
			case 1:
				return "Severe (red)";
			case 2:
				return "High (orange)";
			case 3:
				return "Elevated (yellow)";
			case 4:
				return "Guarded (blue)";
			case 5:
				return "Low (green)";
			default:
				return "Unknown";
		}
	}

	public any function onMissingMethod(
		required string missingMethodName,
		required struct missingMethodArguments
	) {

		try {
			
			// see if we're proxying a method call we want to ensure is called as expected
			var targetMethodName = rereplace( arguments.missingMethodName, "^\$_", "" );
			var targetMethodArguments = arguments.missingMethodArguments.keyExists("args") ? arguments.missingMethodArguments.args : {}; 

			if( structkeyexists( this, targetMethodName ) ) {
				return invoke( this, targetMethodName, targetMethodArguments );
			}
			else {
				throw( type="InvalidMethod", message="The " & arguments.targetMethodName & " method does not exist" );
			}

		}
		catch( any e ) {
			rethrow; 
		}
	}

}
where you prefix your private methods with $_ within your tests, but the method signatures are unmodified
Copy code
getThreatLevelName( becomes $_getThreatLevelName( when you're calling it from the test
t
I'm going to try it for shits and giggles, but moving the function to another component works and is much simpler.
🤦 That doesn't work because onMissingMethod is public... so like everything else, it tries calling it as a
onMissingMethod(expectation, args)
so you just get an error that
missingMethodName is not a string
It was a good thought though.
b
Might it be that
level
is being passed as a string? What happens when you test the following:
Copy code
var level = args.keyExists("1") && IsNumeric(args[1]) ? args[1] : 0;

level=val(level);

expectation.message = args.keyExists("message") ? args.message : "The alert has [#records.len()#] record(s) at [#getThreatLevelName(level)#].";