SilverStripe 4 : FunctionalTest "get" method returns 404 status although the page is there. - phpunit

I am trying to test a controller with this Test class,
<?php
use SilverStripe\Dev\FunctionalTest;
class SitePageControllerTest extends FunctionalTest
{
protected static $fixture_file = 'site/tests/fixturesSitePage.yml';
public function testViewSitePage()
{
$obj = $this->objFromFixture('SitePage', 'page1');
$page = $this->get('page-one/');
$this->assertEquals(200, $page->getStatusCode());
}
}
and Fixture.
SitePage:
page1:
Title: Page One
CanViewType: true
But "$this->get('page-one/');" returns a 404 page.

Pages are versioned, and this one isn't published at the point where you ask for it, so the functional test emulates a frontend web request which is served from the live (published) stage by default.
You can use the draft site by appending ?stage=Stage to your request URL, or by using protected static $use_draft_site = true in your functional test (this is deprecated in 4.2).
Note that FunctionalTest doesn't log a user in, so you may also need to log in with some level of permission i.e. $this->logInWithPermission('ADMIN')

Another option is to publish it using something like: $obj->publishRecursive(); before the get()

Related

AEM Servlet not getting executed

I have a servlet with OSGI annotation like below
#Component( immediate = true, service = Servlet.class, property = { "sling.servlet.extensions=json",
"sling.servlet.paths=/example/search", "sling.servlet.methods=get" } )
public class SearchSevrlet
extends SlingSafeMethodsServlet {
#Override
protected void doGet( final SlingHttpServletRequest req, final SlingHttpServletResponse resp )
throws ServletException, IOException {
log.info("This is not getting called ");
}
}
But When i try to hit the servlet with JQuery
$.get( "/example/search.json", function( data ) {
$( ".result" ).html( data );
alert( "Load was performed." );
});
I am getting below information rather than servlet getting executed.
{"sling:resourceSuperType":"sling/bundle/resource","servletClass":"com.group.aem.example.servlet.SearchSevrlet","sling:resourceType":"/example/search.servlet","servletName":"com.group.aem.example.servlet.SearchSevrlet"}
Please let me know if i need to make any other configuration.
The info that you are getting is the answer of the Default JSON Servlet
Please read this: Servlets and Scripts
You are registering the "SearchServlet" with the property "sling.servlet.paths". This property is defined as:
sling.servlet.paths: A list of absolute paths under which the servlet is accessible as a Resource. The property value must either be a single String, an array of Strings...
That means that your servlet will be only triggered if you request the same exact path, in this case "/example/search", like this:
GET /example/search
I would recommend you to use the properties "resourceTypes" and "selectors" in your Servlet rather than "paths". For example, a better configuration could be:
property = {
"sling.servlet.resourceTypes=/example/search.servlet",
"sling.servlet.selectors=searchselector",
"sling.servlet.extensions=json",
"sling.servlet.methods=GET"
}
With this config, your SearchServlet should be triggered with a GET request to a resource with resourceType="/example/search.servlet", with the selector "searchselector" and the extension "json". For example:
GET /corcoran/search.searchselector.json
I had a similar problem with yours.
To find out what is wrong, I checked "Recent Requests" page.
(at http://localhost:4502/system/console/requests.)
In my case, there was a log saying, "Will not look for a servlet at (my request path) as it is not in the list of allowed paths".
So I moved to "Config Manager" page(at http://localhost:4502/system/console/configMgr), and searched for "Apache Sling Servlet/Script Resolver and Error Handler".
It has a list named "Execution Paths", and I added my request path to the list.
After adding my path to the list, the problem is solved.

How is the callback from a resource owner processed in HWIOAuthBundle?

I am trying to understand how HWIOauthBUndle works. I can see how the initial authorization request to a resource owner is built and made.
I do not see however, how a callback made from a resource owner triggers any controller/action in my application (which it most obviously does, though).
When following the generally available instructions, the callback will be made to something like <path to my app>/check-[resourceOwner], e.g. http://www.example.com/oauth/check-facebook.
In my routing.yml file, I put
facebook_login:
pattern: /oauth/check-facebook
I don't see how any controller is associated with that route, so what actually happens when a callback is made to my application?
The authentication provider system is one of the more complicated features. You will probably want to read through here: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
Callbacks are handled through a request listener. Specifically:
namespace HWI\Bundle\OAuthBundle\Security\Http\Firewall\OAuthListener;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
class OAuthListener extends AbstractAuthenticationListener
{
public function requiresAuthentication(Request $request)
{
// Check if the route matches one of the check paths
foreach ($this->checkPaths as $checkPath) {
if ($this->httpUtils->checkRequestPath($request, $checkPath)) {
return true;
}
}
return false;
}
protected function attemptAuthentication(Request $request)
{
// Lots of good stuff here
How checkPaths get's initialized and how all the calls are made would require a very long explanation. But the authentication provider chapter will get you going.

Symfony 2: how to have the firewall custom redirect upon custom event

my symfony app deals with multiple "blogs" (multisite wordpress style)
I have some special status for my blogs among which : closed_status.
I created a Voter for some special rights and that allows me to know if the voter should allow access or not upon verification that the blog we're accessing is open/closed...
** example url : http://blog01.myapp.com http://blog18.myapp.com ....
For now i throw a 403 error and use custom management of 403 exception but i'd like to avoid that and rather redirect at the momment of the voter processing my request.
** like what happens when you're not authenticated and you're being redirected to /login or such.
I'm not asking for a full solution, could you just tell me what classes/concepts i should be looking into ?
I thought or listeners+entryPoints thingy but don't really get how it works.
It seems to me that this needAuthentication ==> redirect to login thing is very spécifically coded in symfony.
ps: I'm using fosUserBundle and tried to override as little as i could withing the bundle and concerning hte firewall too.
The flow i'd like to achieve:
request to a blog url
=> myCustomVoter denies and dispatch(CLOSED) or dispatch(PRIVATE) or dispatch(VIPONLY)
=> and it results in a clean redirection to my setpsController actions (defined somewhere)
Dispatch a custom event in a voter. In the event listener for that event set response to RedirectResponse
Ok,
I noticed (think) that i can't add a listener to the current firewall (tell me if I'm wrong).
I also noticed that to do what i want i need to setResponse to a GetResponseEvent.
So I just created a generic listener, not attached to the firewall:
It listens to my custom event and the kernel.request that is a GetResponseEvent
For now I'm just hopping all will be listened in the order that suits me:
my service determinig if the blog is open, public... and that will eventually dispatch my custom event
my custom listener that will receive the custom event and prepare a response
my custom listener that receive the kernel.request and inject my calculated response if it exists
For my first tests it seems to work, but i need to test extensively to be sure that the firewall listeners don't mess with my process (it should not i think)
Here is how i defined my listener.
route_access_listener:
class: [...]\RouteAccessListener
arguments:
- #service_container
tags:
- { name: kernel.event_listener, event: routeAccessRedirect, method: onCustomRouteAccess }
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Here is the implementation of my listener:
[...]
public function onKernelRequest(GetResponseEvent $event)
{
if($this->response)
$event->setResponse($this->response);
}
public function onCustomRouteAccess(RouteAccessEvent $event)
{
$type = $event->getType();
switch($type){
case RouteAccessManager::DR_IS_USER_OF_OTHER_BLOG:
$redirectPath = 'link_to_blog';
break;
case RouteAccessManager::DR_BLOG_CLOSED:
$redirectPath = 'info_closed_blog';
break;
case RouteAccessManager::DR_PRIVATE_BLOG:
$redirectPath = 'info_private_blog';
break;
case RouteAccessManager::DR_USER_NOT_ENABLED_BLOG:
$redirectPath = 'info_not_enabled_on_blog';
break;
case RouteAccessManager::DR_INVALID_HOSTNAME:
$redirectPath = 'info_invalid_hostname';
break;
}
$url = $this->container->get('router')->generate($redirectPath);
$this->response = new RedirectResponse($url);
}

Overriding 404 header in extended ErrorPage[_Controller] with SilverStripe 3.1

I have an "intelligent error page" class that extends ErrorPage and ErrorPage_Controller, Basically what it does it a) detect if it's a 404, then b) tried to locate a potential redirect page based on some custom search logic. If the page is found elsewhere, the user is redirected automatically to that location. I know SilverStripe has a basic version of this already based on renamed / moved SiteTree elements, however this is more advanced.
Anyway, since 3.1, it seems impossible to override the 404 header sent (although this worked fine for 3.0).
class IntelligentErrorPage_Controller extends ErrorPage_Controller {
public function init() {
parent::init();
$errorcode = $this->failover->ErrorCode ? $this->failover->ErrorCode : 404;
if ($errorcode == 404) {
... some search logic ...
if ($RedirectSiteTreePage)
return $this->redirect($RedirectSiteTreePage->Link());
}
}
}
As of 3.1 the above returns both a "HTTP/1.1 404 Not Found" as well as the "Location: [url]" header - however it seems impossible to override the 404 status.
Any idea how I can restore the intended "HTTP/1.1 302 Found" header?
PS: I've tried $this->getResponse()->setStatusCode(302) etc with no luck either.
The init() function is called by ModelAsController, and as this class is not able to find a suitable old page for a random url segment, it rebuilds the http response after you built your own response, and therefore overrides the 302 with a 404. This happens at line 130 of ModelAsController. A way to circumvent that is to change the approach and throw an exception, that will prevent the call to getNestedController. Handily, there's such an exception, called SS_HTTPResponse_Exception.
This snippet works for me (redirects to the contact us page with a 302):
<?php
class IntelligentErrorPage extends ErrorPage {
}
class IntelligentErrorPage_Controller extends ErrorPage_Controller {
public function init() {
parent::init();
$errorcode = $this->failover->ErrorCode ? $this->failover->ErrorCode : 404;
if ($errorcode == 404) {
//... some search logic ...
$response = new SS_HTTPResponse_Exception();
$response->getResponse()->redirect('contact-us');
$this->popCurrent();
throw $response;
}
}
}

Symfony2 Templating without request

I'm trying to send an email from a ContainerAwareCommand in Symfony2. But I get this exception when the email template is render by:
$body = $this->templating->render($template, $data);
Exception:
("You cannot create a service ("templating.helper.assets") of an inactive scope ("request").")
I found in github that this helper need the request object. Anybody knows how can I to instance the Request object?
You need to set the container into the right scope and give it a (fake) request. In most cases this will be enough:
//before you render template add bellow code
$this->getContainer()->enterScope('request');
$this->getContainer()->set('request', new Request(), 'request');
The full story is here. If you want to know the details read this issue on github.
The problem arises because you use asset() function in your template.
By default, asset() relies on Request service to generate urls to your assets (it needs to know what is the base path to you web site or what is the domain name if you use absolute asset urls, for example).
But when you run your application from command line there is no Request.
One way to fix this it to explicitely define base urls to your assets in config.yml like this:
framework:
templating:
assets_base_urls: { http: ["http://yoursite.com"], ssl: ["http://yoursite.com"] }
It is important to define both http and ssl, because if you omit one of them asset() will still depend on Request service.
The (possible) downside is that all urls to assets will now be absolute.
Since you don't have a request, you need to call the templating service directly like this:
$this->container->get('templating')->render($template, $data);
Following BetaRide's answer put me on the right track but that wasn't sufficient. Then it was complaining: "Unable to generate a URL for the named route "" as such route does not exist."
To create a valid request I've modified it to request the root of the project like so:
$request = new Request();
$request->create('/');
$this->container->enterScope('request');
$this->container->set('request', $request, 'request');
You might need to call a different route (secured root?), root worked for me just fine.
Symfony2 Docs
Bonus addition:
I had to do so much templating/routing in cli through Symfony2 commands that I've updated the initializeContainer() method in AppKernel. It creates a route to the root of the site, sets the router context and fakes a user login:
protected function initializeContainer()
{
parent::initializeContainer();
if (PHP_SAPI == 'cli') {
$container = $this->getContainer();
/**
* Fake request to home page for cli router.
* Need to set router base url to request uri because when request object
* is created it perceives the "/portal" part as path info only, not base
* url and thus router will not include it in the generated url's.
*/
$request = Request::create($container->getParameter('domain'));
$container->enterScope('request');
$container->set('request', $request, 'request');
$context = new RequestContext();
$context->fromRequest($request);
$container->get('router')->setContext($context);
$container->get('router')->getContext()->setBaseUrl($request->getRequestUri());
/**
* Fake admin user login for cli. Try database read,
* gracefully print error message if failed and continue.
* Continue mainly for doctrine:fixture:load when db still empty.
*/
try {
$user = $container->get('fos_user.user_manager')->findUserByUsername('admin');
if ($user !== null) {
$token = $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$this->getContainer()->get('security.token_storage')->setToken($token);
}
} catch (\Exception $e) {
echo "Fake Admin user login failed.\n";
}
}
}
You might not need the last $container->get('router')->getContext()->setBaseUrl($request->getRequestUri()); part, but I had to do it because my site root was at domain.com/siteroot/ and the router was stripping /siteroot/ away for url generation.

Resources