I'm trying to consume Broadleaf APIs to Create Cart, Add Item and Checkout on a consumer application.
Cloned Demo application and modified the configuration as per the link:
https://www.broadleafcommerce.com/docs/core/current/broadleaf-concepts/rest/rest-tutorials
Problems:
1. Create new Cart->
POST: http://localhost:8080/api/v1/cart
Exception: HttpRequestMethodNotSupportedException: Request method 'POST' not supported
With GET request: worked
Add Product ID:
POST: http://localhost:8080/api/v1/cart/1?categoryId=1&customerId=100
Exception:HttpRequestMethodNotSupportedException: Request method 'POST' not supported
GET Request worked but the product is not added.
3.Add a Payment to the Order
POST: http://localhost:8080/api/v1/cart/checkout/payment?customerId=100
Added the OrderPaymentWrapper in the body as mentioned in the above URL
Exception:
messageKey": "queryParameterNotPresent",
"message": "com.broadleafcommerce.rest.api.exception.BroadleafWebServicesException.queryParameterNotPresent"
Alternatively, referred https://demo.broadleafcommerce.org/api/v2/swagger-ui.html#/ to invoke the API as per the swagger documentation.
Same issue, unable to create a order flow.
I've tried to debug by running on localhost https://github.com/BroadleafCommerce/DemoSite
Same issue.
Please advise.
This looks like an outstanding issue with our #FrameworkController annotation. I opened an issue in Broadleaf at https://github.com/BroadleafCommerce/Issues/issues/3 with more information as to why it is currently failing.
The workaround is to modify CustomCartEndpoint in the API project that you have to add in the createNewCartForCustomer() method. The final implementation of CustomCartEndpoint should look like this:
#RestController
#RequestMapping(value = "/cart",
produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public class CustomCartEndpoint extends CartEndpoint {
#Override
#RequestMapping(value = "", method = RequestMethod.GET)
public OrderWrapper findCartForCustomer(HttpServletRequest request) {
try {
return super.findCartForCustomer(request);
} catch (Exception e) {
// if we failed to find the cart, create a new one
return createNewCartForCustomer(request);
}
}
#Override
#RequestMapping(value = "", method = RequestMethod.POST)
public OrderWrapper createNewCartForCustomer(HttpServletRequest request) {
return super.createNewCartForCustomer(request);
}
}
Related
I would like to show my requests and responses details in my HTML report.
A feature file example:
Feature: Rest Assured under Cucumber POC
Scenario: Azure Login Scenario
Given Request specifications are set with base uri "https://login.microsoftonline.com/"
When Azure Login Request Executed
Then Verify Status Code is 200
The Runner class is:
#RunWith(Cucumber.class)
#CucumberOptions(
features = "src/main/resources/features",
glue = {""},
tags = "#tests",
plugin = { "pretty",
"json:target/cucumber-reports/Cucumber.json",
"html:target/cucumber-reports"}//reporting plugin
)
public class CucumberRunner {}
The steps are:
#Given("Request specifications are set with base uri {string}")
public void setRequestsSpec(String baseUri){
RequestSpecification spec = new RequestSpecBuilder()
.setBaseUri(baseUri)
.addFilter(new ResponseLoggingFilter())//log request and response for better debugging. You can also only log if a requests fails.
.addFilter(new RequestLoggingFilter())
.build();
testContext().setRequestSpec(spec);
}
#When("^Azure Login Request Executed$")
public void azureLoginExecuted() {
response =
given() //Add x-www-form-urlencoded body params:
.formParam(GRANT_TYPE_KEY, GRANT_TYPE_VALUE)
.formParam(AUTO_TEAM_CLIENT_ID_KEY, AUTO_TEAM_CLIENT_ID_VALUE)
.formParam(AUTO_TEAM_CLIENT_SECRET_KEY, AUTO_TEAM_CLIENT_SECRET_VALUE)
.formParam(RESOURCE_KEY, RESOURCE_VALUE)
.when()
.post(AUTO_TEAM_TENANT_ID + RESOURCE); //Send the request along with the resource
testContext().setResponse(response);
setAuthorizationToken();
}
#Then("Verify Status Code is {int}")
public void verifyStatusCode(int expected_repsonse_code) {
testContext().getResponse().then().statusCode(expected_repsonse_code);
}
Currently I found out how to show those details only in my IntelliJ console:
For example:
#tests
Feature: Rest Assured under Cucumber POC
#tests
Scenario: Azure Login Scenario # src/main/resources/features/poc.feature:5
Given Request specifications are set with base uri "https://login.microsoftonline.com/" # CommonStepsDefinitions.setRequestsSpec(String)
Request method: POST
Request URI: https://login.microsoftonline.com/6ae4e000-b5d0-4f48-a766-402d46119b76/oauth2/token
Proxy: <none>
Request params: <none>
Query params: <none>
and more..
But the HTML report shows only:
Thank you!
You can log the RestAssured output to your cucumber scenario report by just setting up your RequestSpecification filters in this way:
ByteArrayOutputStream requestResponseBuffer = new ByteArrayOutputStream();
PrintStream requestResponsePrintStream = new PrintStream(requestResponseBuffer , true);
List<Filter> loggingFilters = Arrays.asList(
new RequestLoggingFilter(requestResponsePrintStream),
new CucumberReportLoggingFilter(requestResponseBuffer, scenario),
new ResponseLoggingFilter(requestResponsePrintStream)
);
RequestSpecification requestSpecification = RestAssured.given()
.filters(loggingFilters)
:
where the CucumberReportLoggingFilter class looks like this:
class CucumberReportLoggingFilter implements Filter {
ByteArrayOutputStream requestResponseBuffer;
Scenario scenario;
CucumberReportLoggingFilter(ByteArrayOutputStream requestResponseBuffer, Scenario scenario) {
this.requestResponseBuffer = requestResponseBuffer;
this.scenario = scenario;
}
#Override
Response filter(FilterableRequestSpecification requestSpec,
FilterableResponseSpecification responseSpec, FilterContext ctx) {
// Call the next filter(s) which logs the response to the requestResponseBuffer
Response response = ctx.next(requestSpec, responseSpec);
scenario.write(requestResponseBuffer.toString());
return response;
}
}
This solution sandwiches the custom CucumberReportLoggingFilter between the standard RequestLoggingFilter and ResponseLoggingFilter filters.
The sequence of filter events that occur when the RequestSpecification sends a Request is:
The standard RequestLoggingFilter filter() method runs and pretty prints the Request details to the requestResponseBuffer.
The custom CucumberReportLoggingFilter filter() method runs and calls the next filter (which is the standard ResponseLoggingFilter filter).
The standard ResponseLoggingFilter filter() method runs and pretty prints the Response details to the requestResponseBuffer.
Control is returned to the custom CucumberReportLoggingFilter filter() method which sends the contents of the requestResponseBuffer to the cucumber report using the scenario.write() method.
Only write out the messages if the scenario fails
You could modify the above solution as follows:
Remove the CucumberReportLoggingFilter.
Make the requestResponseBuffer variable 'scenario-scoped' (i.e. only have one buffer per scenario).
Have the last #After hook that runs in your stepdefs code check the scenario result and if it is failure, write out the contents of the requestResponseBuffer to the cucumber report.
I can give you some details which might not answer your question fully.
In order to add data to Cucumber HTML report you can use:
#After
public void addDataToReport(Scenario scenario) { //scenario is provided from Cucumber
scenario.write(string with the information about scenario);
}
It won't be formatted and I don't know how to change how the report displays it. Each of the messages will be under each Test Case.
You have to, somehow, pass the information to #After hook.
I hope someone else will answer with more details.
EDIT:
In order to store the info about what Scenario is running at the moment, or even in parallel, we can create a class to store necessary information, based on the Thread, so it will be Thread-safe.
Let's create a class to store Scenario. Let's call it Storage
public class Storage {
private static final HashMap<Thread, Scenario> map = new HashMap<>();
public static void putScenario(Scenario scenario) {
map.put(Thread.currentThread(), scenario);
}
public static Scenario getScenario() {
return map.get(Thread.currentThread());
}
}
Now, we have to somehow get the Scenario. It can be achieved by using #Before hook like this:
public class BeforeHook {
#Before(order = 1)
public void getScenario(Scenario scenario) {
Storage.putScenario(scenario);
}
}
#Before hooks are run before each scenario. We get the information about Scenario and put it in Storage so we know what Scenario is run on what Thread.
Remember that hooks have to be reachable by the glue parameter in Cucumber Runner!
And now, if we want to write additional information to the report:
#Then("Data is saved to the report")
public void data_is_saved_to_the_report() {
System.out.println("Saving data to report");
Storage.getScenario().write("Test data and stuff");
}
We just get current scenario from the Storage and use Scenario.write() method to add information to the report.
It will look like this in the report:
So, I have an MVC5 site that uses the default routing template {controller}/{action}/{id} and this works fine. Most everything in the site requires a login (i.e. [Authorize] attribute is used almost everywhere), and this works fine.
Well, now I have a need to allow anonymous access to select pages when a certain kind of link pattern is used: App/{token}/{action}. The {token} is a random string associated with something in my database. I can issue and deactivate these tokens at will.
I got this new App/{token}/{action} routing working by implementing a custom RouteBase that parses the incoming URL for these tokens, and, crucially, adds the the token value to the RouteData.DataTokens so that my App controller can make use of it without needing an explicit action argument for it. So, I added this new route to the route table ahead of the default routing like this:
// new route here
routes.Add("AppToken", new AnonAppAccessRoute());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Here is the problem/question: adding this now route has now made my default route stop working -- everything is now going through AnonAppAccessRoute which of course is meant to work only for a few things. I don't understand how to make my AnonAppAccessRoute apply only to URLs with a certain pattern. The MapRoute method accepts a URL pattern, but Adding a route doesn't seem to let you put a filter on it. What am I missing? I've looked around quite a bit at various blogs and documentation about routing, but I've not found good info about using the DataTokens collection (which I feel is important to my approach), and I'm not seeing a good explanation of the difference between Adding a route explicitly vs calling MapRoute.
Here's the code of my custom RouteBase:
public class AnonAppAccessRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string[] pathElements = httpContext.Request.Path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (pathElements.Length > 0)
{
string token = TryGetArrayElement(pathElements, 1);
if (!string.IsNullOrEmpty(token))
{
result = new RouteData(this, new MvcRouteHandler());
result.DataTokens.Add("appToken", token);
result.Values.Add("controller", "App");
result.Values.Add("action", TryGetArrayElement(pathElements, 2, "Index"));
}
}
return result;
}
private string TryGetArrayElement(string[] array, int index, string defaultValue = null)
{
try
{
return array[index];
}
catch
{
return defaultValue;
}
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
I got this to work by dropping the custom RouteBase and instead used this MapRoute call like this:
routes.MapRoute(
name: "AppAnon",
url: "App/{token}/{action}",
defaults: new { controller = "App", action = "Index" }
);
Then, in my App controller, I did this in the Initialize override:
protected AppToken _appToken = null;
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
string token = requestContext.RouteData.Values["token"]?.ToString();
_appToken = Db.FindWhere<AppToken>("[Token]=#token", new { token });
if (!_appToken?.IsActive ?? false) throw new Exception("The token is not found or inactive.");
}
This way, my "token" is available to all controller actions via the _appToken variable, and already validated. I did not need to use RouteData.DataTokens. Note that my Db.FindWhere statement is ORM-specific and not really related to the question -- it's just how I look up a database record.
I'm trying to get the edit URL of a content as a string from backend, the catch is I'm inside a workflow activity, so I can't use Url.Action... or Url.ItemEditLink... or other UrlHelpers as if it were a controller or a view. Also, although I'm inside a workflow, the contents I need it for are not part of the workflowContext or the activityContext, so I can't use those or tokens either.
A solution could be to get the content metadata and the site baseUrl and try to build it manually, but I think this way is prone to errors.
Thanks.
This is how I build a Uri in an activity:
public class MyClass : Task
{
private readonly RequestContext _requestContext;
...
public MyActivity(RequestContext requestContext, ...)
{
_requestContext = requestContext;
...
}
...
public override IEnumerable<LocalizedString> Execute(WorkflowContext workflowContext, ActivityContext activityContext)
{
var content = ... get using ID
var helper = new UrlHelper(_requestContext);
var baseurl = new Uri(_orchardServices.WorkContext.CurrentSite.BaseUrl);
Uri completeurl = new Uri(baseurl, helper.ItemDisplayUrl(content));
yield return T("Done");
}
}
Turns out that I actually do build the Uri semi-manually, but I haven't had issues with this method. You may be able to use just the ItemDisplayUrl for navigation inside of Orchard; I had to get the full URL because the string gets sent to an outside program (Slack).
In a small Azure Mobile App, I have the following GET method in a tablecontroller:
public IQueryable<User> GetAllUser()
{
return Query();
}
using the following REST call, I can query users with the lastname='Tiger'
GET: ~/userinfo?$filter=lastName%20eq%20'Tiger'
Now I'd like to add an organisation fields to my user, so I've changed the get method to:
public IQueryable<UserDto> GetAllUser()
{
return Query().Select(u => new UserDto{FirstName=u.FirstName, LastName=u.LastName, Organisation="Acme"});
}
but now, when I try to query my users, using the same filter:
GET: ~/userinfo?$filter=lastName%20eq%20'Tiger'
I get an 'Bad Request' error....
How can I make this work...
Any help would be greatly appreciated....
Try:
public IQueryable<UserDto> GetAll()
{
return Query().AsEnumerable().Select(u =>
new UserDto
{
FirstName = u.FirstName,
LastName = u.LastName,
Organisation = "Acme"
}).AsQueryable();
}
Your ODATA query will be used on the DTO and not the entity.
Your Controller should still use TableController<User>.
You could also use AutoMapper and simply do:
return Query().ProjectTo<UserDTO>();
This will do LINQ to Entities.
Your post helped me solve my own issue!
From what I can tell, it doesn't work because when you're using DTO objects and MappedEntityDomainManager, this.Query() call crashes. In my case, both my Model and DTO object inherit from Azure.Mobile.Server.EntityData.
Basically... avoid this.Query() with MappedEntityDomainManager
Try this:
public IQueryable<UserDto> GetAllUser()
{
return _context.User.Select(u => new UserDto{FirstName=u.FirstName, LastName=u.LastName, Organisation="Acme"});
}
where _context is your DBContext and .User is your User DBSet
I added AllowOrigin custom attribue with requested controller action. Refer below function:
public class **AllowCrossSiteJsonAttribute** : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.RequestContext.HttpContext.Response.AddHeader("Access-Control-Allow-Origin", "*");
base.OnActionExecuting(filterContext);<br>
}
}
[**AllowCrossSiteJson**]
public partial class OneBillSummaryController : UXPControllerBase
{
public OneBillSummaryController()
: base(ApplicationType.Home,
new QueryParameters() { appliesTo="Index", parameters=
new Dictionary<string, QueryParameter>() {
{ "BAN", new QueryParameter() },
{ "SeqNo", new QueryParameter() },
{ "CID", new QueryParameter() {values="cc,ss,r"} },
{ "Lang", new QueryParameter() {values="fr,en"}},
{ "TID", new QueryParameter(){isOptional=true} }
}})
{
}
Till here, I was able to get result, but this action will do form authentication (already cookie exist in browser) and another URL/Controller-Action is going to call (Another URL request internally).
And I am stucking with below error. Refer below SS:
XMLHttpRequest cannot load
http://local-mybell-b.ids.int.bell.ca/UXP/Bill/OneBill/?BAN=101465254&SeqNo=0&CID=r&TID=&Lang=en.
The request was redirected to
'http://local-mybell-b.ids.int.bell.ca/sso/ssoauth.aspx?ReturnUrl=%2fUXP%2fB…d0%26CID%3dr%26TID%3d%26Lang%3den&BAN=101465254&SeqNo=0&CID=r&TID=&Lang=en',
which is disallowed for cross-origin requests that require
preflight.
Added same CROS for that authentication action as well, but no luck.
Yes, I got the answer Self, and posting the same.
Use "Access-Control-Allow-Origin" in web-config or controller level as per requirement and Change the authentication mode of web, As I disabled windows auth mode.
Above code will work perfect, just while changing the Auth mode from configuration file.
Refer below SS.