Page 1 of 2
Testing a Request
Posted: Fri Aug 19, 2005 11:42 am
by nielsene
OK I think I'm getting closer to understanding a few things, and I'm trying to start by writing some tests to drive the creation of my first Request object. -- Ie similar to a HttpRequest in J2EE. Wraps the GET/POST, provides named accessors, etc, pulls out pieces of the URL as needed, etc.
How do you normally test this? manually fiddle with $_GET/$_POST to get the data into them that the request needs? Assign into $_SERVER to change PHP_SELF or REQUEST_URI, etc? I want to keep this at a unit test level, and not actually issueing web requests, etc.
(Not a testing question? but do you tend to have a Request validate/santize the data at creation time or use time(lazy))
Posted: Fri Aug 19, 2005 12:11 pm
by timvw
- I do this by manually creating the $_* arrays (from the developper POV this is the same as mocking the webserver)
I choose to:
- sanitize the input before it's placed into the Request (magic quotes garbage etc..)
- not to perform validation in the Request. This is handled by the Actions that are called by the controller. Each action is responsible for knowing what it's allowed to do (and thus the input it accepts/ pulls from the request)
Posted: Fri Aug 19, 2005 12:52 pm
by nielsene
Here's what I have so far, this covers the "easy" inputs, the next batch is variable depending on items from the DB (ie it will be a dynamic form that submits to here.) Before I tackle that harder bit are there any comments?
Tests:
Code: Select all
<?php
require_once(COMPINABOX.'include/classes/requests/ChesterExportRequest.inc');
class TestChesterExportRequest extends UnitTestCase {
function TestChesterExportRequest() {
$this->UnitTestCase('TestChesterExportRequest');
}
function testExtractUnixName() {
$compUnix='SD';
$_SERVER["PHP_SELF"]="/register/$compUnix/admin/ChesterExport.php";
$httpRequest = new ChesterExportRequest();
$this->assertEqual($httpRequest->getCompUnix(),"$compUnix");
}
function testEliminateGet() {
$key="SampleGet";
$_GET[$key]="foo";
$httpRequest = new ChesterExportRequest();
$this->assertTrue(!isset($_GET[$key]));
}
function testGetSubmit_Mapping() {
$submitKey="Submit";
$submitValue="Update Mappings";
$_POST[$submitKey]=$submitValue;
$httpRequest= new ChesterExportRequest();
$this->assertEqual($httpRequest->getSubmit(),$submitValue);
}
function testGetSubmit_Export() {
$submitKey="Submit";
$submitValue="Generate Export";
$_POST[$submitKey]=$submitValue;
$httpRequest= new ChesterExportRequest();
$this->assertEqual($httpRequest->getSubmit(),$submitValue);
}
function testGetSubmit_Other() {
$submitKey="Submit";
$submitValue="Something Else";
$_POST[$submitKey]=$submitValue;
$httpRequest= new ChesterExportRequest();
$this->assertEqual($httpRequest->getSubmit(),"");
}
function testGetDeliveryAddress() {
$email="foo@example.net";
$_POST["email"]=$email;
$httpRequest= new ChesterExportRequest();
$this->assertEqual($httpRequest->getDeliveryAddress(),$email);
}
}
?>
Implementation:
Code: Select all
<?php
class ChesterExportRequest {
var $_legalSubmit = array("Update Mappings","Generate Export");
var $_cup;
function ChesterExportRequest() {
$_cup=array();
$this->_purgeGet();
foreach ($gets as $aKey) unset($_GET[$aKey]);
if (preg_match("#register/([-A-Za-z0-9_]*)/#",$_SERVER["PHP_SELF"],
$matches))
$this->_cup["CompUnix"]=$matches[1];
}
function _purgeGet() {
$gets = array_keys($_GET);
foreach ($gets as $aKey) unset($_GET[$aKey]);
}
function getCompUnix() {
if (isset($this->_cup["CompUnix"]))
return $this->_cup["CompUnix"];
else
die("ChesterExportRequest Reached without compunix in URL");
}
function getDeliveryAddress() {
if (!isset($_POST["email"])) return "";
return trim($_POST["email"]);
}
function getSubmit() {
if (!isset($_POST["Submit"])) return "";
return (in_array($_POST["Submit"],$this->_legalSubmit))?$_POST["Submit"]:
"";
}
}
?>
Re: Testing a Request
Posted: Fri Aug 19, 2005 1:01 pm
by McGruff
nielsene wrote:How do you normally test this? manually fiddle with $_GET/$_POST to get the data into them that the request needs? Assign into $_SERVER to change PHP_SELF or REQUEST_URI, etc?
Yes. You might want to save/restore superglobals in setup/teardown:
Code: Select all
function setUp()
{
$this->_server_cache = $_SERVER;
$_SERVER = array();
}
function tearDown()
{
$_SERVER = $this->_server_cache;
}
Setting $_SERVER to an empty array is the kind of thing you'd probably want to do to prevent test methods from interfering with each other. However, if you're running test groups, other test cases in the group might be using $_SERVER etc so it's best to restore them to their original state. I've been caught out like that before with a test which had to check the OS (from $_SERVER['SERVER_SOFTWARE']) but I'd wiped the $_SERVER array clean in another test case...
nielsene wrote:(Not a testing question? but do you tend to have a Request validate/santize the data at creation time or use time(lazy))
In general, lazy loading is probably best. If there were very many input parameters - say a big form submission - and the script might bug out if the user isn't authorised, lazy loading would save validating a bunch of vars which might not be used. Probably doesn't matter too much, as a rule.
Posted: Fri Aug 19, 2005 1:07 pm
by nielsene
Added setUp/tearDown that saves/restores Get,Post,Cookie, and Server.
Thanks for the pointer.
Posted: Fri Aug 19, 2005 1:12 pm
by nielsene
should the getDeliveryAdress method try to do token validation on the email? I think timv is saying "no, that's done by the Command/Handler". I think McGruff has said earlier that all validation should occur before letting data out of the request.
Obvisouly some data cab only be valdated in the context of the domain model, so in that case I'd agree with timv; however in the case of hecking if an email has valid syntax, that seems more of a Request level thing?
Posted: Fri Aug 19, 2005 1:19 pm
by McGruff
nielsene wrote:any comments?
Looks good.
One other thing I like to do is always check for "alien" keys in all the GPC arrays even if it's just to assert eg count($_COOKIE) == 0. It would be easy to mis-type a url but unexpected vars in post or cookies are more supicious. Admittedly there might not be a lot you can do about it if you find some, and the additional parameters might not be able to do any damage (you'd have to have some undefined vars somewhere).
Posted: Fri Aug 19, 2005 1:26 pm
by nielsene
McGruff wrote:nielsene wrote:any comments?
Looks good.
One other thing I like to do is always check for "alien" keys in all the GPC arrays even if it's just to assert eg count($_COOKIE) == 0. It would be easy to mis-type a url but unexpected vars in post or cookies are more supicious. Admittedly there might not be a lot you can do about it if you find some, and the additional parameters might not be able to do any damage (you'd have to have some undefined vars somewhere).
Well that's partially what the "eliminateGet" does. Its not detecting the presence of un-anticipated keys, just completely wiping out $_GET as I don't use it. I'll code up a similar one for Cookie (since this code runs after a session start, the session will still be propagated via with GET/COOKIE without problem.
Alien keys in the post is a seperate issue, and yes I probably should attempt to detect them.
Posted: Fri Aug 19, 2005 1:29 pm
by McGruff
nielsene wrote:should the getDeliveryAdress method try to do token validation on the email? I think timv is saying "no, that's done by the Command/Handler". I think McGruff has said earlier that all validation should occur before letting data out of the request.
Obvisouly some data cab only be valdated in the context of the domain model, so in that case I'd agree with timv; however in the case of hecking if an email has valid syntax, that seems more of a Request level thing?
That's a good question. I'm not sure I know the answer.
Some validation stuff is presentation-specific rather than part of the domain - eg XSS isn't an issue on the command line.
I do like the idea of a single front door through which all user input must pass, and which never lets anything invalid in. Doing it all in one place might promote more secure code: possibly it's less likely you'd forget something compared to validation checks scattered throughout the script.
The presentation layer is free to roam across the domain of course so you could define validation rules in domain objects then the request object could grab them to do the actual validating.
I'm not sure really.
Posted: Fri Aug 19, 2005 1:32 pm
by timvw
nielsene wrote:should the getDeliveryAdress method try to do token validation on the email? I think timv is saying "no, that's done by the Command/Handler". I think McGruff has said earlier that all validation should occur before letting data out of the request.
I don't think it belongs in the base Request class. But if you create the Request in a specific controller, i can understand that you "add/load" specific filters..
Edit: It appears i have a feeling that has it's place in a Request, but you would still need validation in your model/bussinness layer and your view/output layer too.
Posted: Fri Aug 19, 2005 1:41 pm
by nielsene
timvw wrote:nielsene wrote:should the getDeliveryAdress method try to do token validation on the email? I think timv is saying "no, that's done by the Command/Handler". I think McGruff has said earlier that all validation should occur before letting data out of the request.
I don't think it belongs in the base Request class. But if you create the Request in a specific controller, i can understand that you "add/load" specific filters..
Edit: It appears i have a feeling that has it's place in a Request, but you would still need validation in your model/bussinness layer and your view/output layer too.
Hmm, I'm thinking, all that validation in each stage -- is that becaue you can't be 100% sure that your data came from the sanitized Request? I mean I can understand the view component having to add on an "htmlspecialcharss" before displaying some stuff. But I woudln't revalidate there, I think.
What about this, have the Request capture the GPC super globals and then empty out the superglobals. This would instatly pinpoint a script trying to avoid the Request.
Posted: Fri Aug 19, 2005 2:08 pm
by timvw
Actually, the type of validation is different. For example, when i think about validation before it's placed into a request is generally done by testing regular expressions.. When i think about validation in the model i think about checking duplicate records, testing if the value is really in an enumeration...
Performing such validation before it's placed in the Reques can become a pita if you don't know which modules will be loaded beforehand (and thus which input is expected/allowed) ..
I would expect each of those modules to perform their own input validation. So, i would be required to build a new Request for each module.
Posted: Fri Aug 19, 2005 2:10 pm
by nielsene
True, but I don't consider that validation, that's "just" business logic

Same thing though....
Posted: Fri Aug 19, 2005 3:39 pm
by nielsene
Hmm I'm working on the dynamic form elements. I've built up the test cases that handles the values of the elements and the general format of the dynamic labels.
However now I want to reject what McGruff would call alien keys -- ie elements that were not on the form. Thus I need to query the Database to come up with a list of possible values. But I'm getting confused as to where the DB reference should come from in the first place.
I have a Registry class that I can get the DB reference from. However given one of McGruff's earlier examples of putting the Session into the Request, I'm wondering if the DB should be contained by the Request as the general data gatekeeper?
Most of the scripts in my application need to connect to two databases. One is common to all, and one is based on a component of the URL. The URL component is a mod_rewrite/forcetype type trick so its not part of the FrontController I think. This is making me wonder if the Request should be creating the DB reference as it parses the "custom URL parameters"...
Thoughts?
Posted: Fri Aug 19, 2005 5:51 pm
by timvw
Why don't you place the Registry in the Request? This way you get a controled access to all datasources..
This way your Controller can pass restrictions to the Registry that performs DB generation.. (For example: some users only have "read-access" to some columns)