Shoulda route matcher with subdomains - minitest

I am using shoulda matchers to test critical routes on Rails 4.2.1 with Ruby 2.2.0 on a new application. I just moved my API namespace to a subdomain, and I can't figure out how to get the shoulda routes matcher (or any other concise routes test) to work.
Here is some example code:
config/routes.rb (using versionist for versioning, but that shouldn't be relevant)
namespace :api, path: '', constraints: { subdomain: 'api'} do
api_version(module: 'V1',
path: {value: 'v1'},
defaults: {format: 'json'}, default: true) do
resources :bills, only: :index
end
end
app/controllers/api/v1/bills_controller.rb
module API
module V1
class Bill < APIVersionsController
# GET api.example.com/v1/bills.json
def index
#bills = Bill.all.limit(10)
render json: #bills
end
end
end
end
test/controllers/api/v1/routing_test.rb
module API
module V1
class RoutingTest < ActionController::TestCase
setup { #request.host = 'http://api.example.com' }
should route('/v1/bills')
.to(controller: :bill, action: :index, format: :json)
end
end
end
Before I was using a subdomain, should route('/api/v1/bills').to(action: :index, format: :json) in my BillsControllerTest worked just fine.
Now, when I run rake test, I get Minitest::Assertion: No route matches "/v1/bills".
I've tried putting this in the BillControllerTest (no change);
I've tried an integration test (route matcher doesn't work);
I've tried setting the host with setup { host! 'api.example.com' } and setup { #request.host = 'api.example.com' }
I've tried putting the full URL in the get request ( { get 'http://api.example.com/v1/bills' } );
and I've tried putting subdomain: 'api' and constraints: subdomain: 'api' anywhere that might make sense.
What is a concise way to do route testing with subdomains/what is the current best practice? Is there a way to get the shoulda route matcher to work with them?

This ended up being a simple fix. I just needed to add a subdomain constraint in the #to method and ensure the #route method had the full url:
module API
module V1
class RoutingTest < ActionController::TestCase
should route(:get, 'http://api.example.com/v1')
.to('api/v1/data#index',
subdomain: 'api',
format: :json)
...
Or, if you are in data_controller_test.rb,
module API
module V1
class DataControllerTest < ActionController::TestCase
should route(:get, 'http://api.example.com/v1')
.to(action: :index,
subdomain: 'api',
format: :json)
...

Related

NestJS: Custom decorator `applyDecorators()` not working with `HttpCode` and `AuthGuard` used together

Scenerio:
I have created two custom decorators as a way to cleanup my controllers by applying all the common swagger decorators, at one place. They are as following.
export function AuthorizedEndpoint(header_key?: string) {
return applyDecorators(
// HttpCode(200), // #Todo Why is it not working?
UseGuards(AuthGuard),
ApiBearerAuth(header_key),
ApiOkResponse({ description: ResponseMessageEnum.R_SUCCESS as string }),
ApiUnprocessableEntityResponse({ description: ResponseMessageEnum.R_BAD_REQUEST as string }),
ApiForbiddenResponse({ description: ResponseMessageEnum.R_UNAUTHORIZED as string }),
ApiNotFoundResponse({ description: ResponseMessageEnum.R_NOT_FOUND as string })
);
}
export function OpenAccessEndpoint() {
return applyDecorators(
HttpCode(200),
ApiOkResponse({ description: ResponseMessageEnum.R_SUCCESS as string }),
ApiUnprocessableEntityResponse({ description: ResponseMessageEnum.R_BAD_REQUEST as string }),
ApiForbiddenResponse({ description: ResponseMessageEnum.R_UNAUTHORIZED as string }),
ApiNotFoundResponse({ description: ResponseMessageEnum.R_NOT_FOUND as string })
);
All my controllers are POST, and nestjs sends 201 as response code. But I want to send 200 instead. Therefore, I used the #HttpCode(200) to send required response code. It is working when using on controllers directly.
ISSUE:
In the custom decorator, which does not implement AuthGuard, HttpCode decorator is working as expected, but in the Authorized Decorator, If I use #HttpCode decorator, I get the following message. If I comment it out, it works as expected and sends 201 default response.
[11:30:25 AM] File change detected. Starting incremental compilation...
[11:30:25 AM] Found 0 errors. Watching for file changes.
C: \Users\Lenovo\Project\node_modules\reflect - metadata\Reflect.js: 541;
var decorated = decorator(target);
^
TypeError: Cannot read properties of undefined(reading 'value')
at C: \Users\Lenovo\Project\node_modules\#nestjs\common\decorators\http\http - code.decorator.js: 17: 87
at C: \Users\Lenovo\Project\node_modules\#nestjs\common\decorators\core\apply - decorators.js: 17: 17
at DecorateConstructor(C: \Users\Lenovo\Project\node_modules\reflect - metadata\Reflect.js: 541: 33)
at Reflect.decorate(C: \Users\Lenovo\Project\node_modules\reflect - metadata\Reflect.js: 130: 24)
at __decorate(C: \Users\Lenovo\Project\dist\administration\controllers\organization.controller.js: 4: 92)
at Object.<anonymous>(C: \Users\Lenovo\Project\src\administration\controllers\organization.controller.ts: 14: 37)
at Module._compile(node: internal / modules / cjs / loader: 1239: 14)
at Object.Module._extensions..js(node: internal / modules / cjs / loader: 1293: 10)
at Module.load(node: internal / modules / cjs / loader: 1096: 32)
at Function.Module._load(node: internal / modules / cjs / loader: 935: 12);
Question:
Is this a bug, or some misconfiguration from my end? What is the explaination of above error message? How do I move ahead from it?
Turns out #HttpCode() is a method decorator and therefore cannot be applied to class.
Therefore either we can not include #HttpCode() in custom decorator, and use it manually on each route method, allowing us to use our custom decorator at class level.
Or we can include it in our custom decorator, and loose the ability of using the decorator at class level.

jHipster gateway downstream prefix

I'm updating old jHipster gateway to 7.5.0 version. New version uses Spring Cloud Gateway (with Eureka), while the old one used Zuul. In previous version working with Service Discovery having service named 'foo' and path 'bar' would register it without any prefix on the gateway so it could be accessed as:
GATEWAY_URL/foo/bar
right now all services register with 'services/' prefix which results requires to call following url:
GATEWAY_URL/services/foo/bar
I can't find configuration responsible for that. I found a property spring.webservices.path, but changing this to other value does not make any change and in Spring Boot 2.6.3 its value cannot be empty or '/' (but Im not sure if this is a property I should be checking). I also experimented with spring.cloud.gateway.routes in form:
spring:
webservices:
path: /test
main:
allow-bean-definition-overriding: true
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: user-service-route
uri: lb://user
predicates:
- Path=/user/**
but without any luck. Also Im not sure if this is jHipster issue or SCG
I need to change that so that other systems using my API won't need to update their paths, I know I can always add nginx before so that it will rewrite te path but that feels not correct.
This behavior is done by SCG autoconfiguration - GatewayDiscoveryClientAutoConfiguration, it registers DiscoveryLocatorProperties bean with predicate:
PredicateDefinition{name='Path', args={pattern='/'+serviceId+'/**'}}
I didnt want to change autoconfigration, so I did WebFilter that is executed as first one and mutates request path
public class ServicesFilter implements WebFilter {
private final ServicesMappingConfigration mapping;
public ServicesFilter(ServicesMappingConfigration mapping) {
this.mapping = mapping;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
RequestPath path = exchange.getRequest().getPath();
if (path.elements().size() > 1) {
PathContainer pathContainer = path.subPath(1, 2);
if (mapping.getServices().contains(pathContainer.value())) {
ServerHttpRequest mutatedRequest = exchange
.getRequest()
.mutate()
.path("/services" + exchange.getRequest().getPath())
.build();
ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
return chain.filter(mutatedExchange);
}
}
return chain.filter(exchange);
}}

How to globally apply Swagger UI's #ApiBearerAuth() instead of applying for each controller

I am working on a nestJS based api, and I am using Swagger UI documentation. I want keep the functionality of #ApiBearerAuth() for all my controllers, however I wish to have this as a global feature. That way every time I create new routes or endpoints it will already be covered.
From the https://docs.nestjs.com/openapi/security documentation:
#ApiBearerAuth()
#Controller('cats')
export class CatsController {}
This is what I am following now, but is there a way to set this globally?
Yes, in your DocumentBuilder you may add:
.addSecurity('ApiKeyAuth', {
type: 'apiKey',
in: 'header',
name: 'Authorization',
})
.addSecurityRequirements('ApiKeyAuth')
In the source code of #nestjs/swagger its visible that addBearerAuth() sets the security name to "bearer" so it is possible to use addBearerAuth() and addSecurityRequirements('bearer'):
const config = new DocumentBuilder()
...
.addBearerAuth()
.addSecurityRequirements('bearer')
.build();
This is a little hack, because I use ApiTags in every Controller so that I create a new decorator
import { applyDecorators } from '#nestjs/common';
export function ApiTagsAndBearer(...tags: string[]) {
return applyDecorators(
ApiBearerAuth(), //
ApiTags(...tags),
);
}

how to use optional url parameters with NestjS

I'm trying to replace our current backend service using Nestjs library,
however, I want to create a route with 2 optional parameters in the URL something like :
/route/:param1/config/:OptionalParam3?/:OptionalParam3?
that means the route should catch :
route/aa/config
route/aa/config/bb
route/aa/config/bb/cc
how can I achieve that, I have tried to use ? and () but it's not working well.
If you are looking for how to annotate an optional query parameter, you can do it like so:
#ApiQuery({
name: "myParam",
type: String,
description: "A parameter. Optional",
required: false
})
async myEndpoint(
#Query("myParam") myParam?: string
): Promise<blah> {
[...]
}
Router params name should be unique. The correct route path is:
Existing one is:
/route/:param1/config/:OptionalParam3?/:OptionalParam3?
Correction:
/route/:param1/config/:OptionalParam3?/:OptionalParam4?
Opinion: You can use query params if the params are optional. It is never a good idea to create optional param routes (disagreements agreed). Both serve the same purpose, but having them as the query params makes it more understandable for debugging and fellow developers.
I solved this problem by using #Query decorator as below:
Here is my controller:
#Get()
async getAll(#Query('someParameter') someParameter?: number) {
return this.service.getAll(someParameter);
}
Here is my client (Angular) service:
getAll(someParameter?: number) {
return this.http.get(`apiUrl/controllerAddress?someParameter=${someParameter}`
);
}
You can use this structure:
-route
-aa
-config
-[[...id]].js
It will work for the routes :
route/aa/config/{anything}

how to add a node import statement to a file with angular schematics?

I want a custom schematic that will create a page in my application. To do this, I want to use the existing core schematics to create a routing module, then create a component in that module to display my page content, and finally wire everything together. It's the "wire everything together" that has me stumped. I can't find clear guidance on the "right" way to to this. Should I:
a) directly modify the existing files that the "module" and "component" schematics created? (I don't know how to do that)
b) use templates to somehow merge the modifications into the files? (I don't know how to do this either)
Below is the index.ts file for my page schematic as of now. What I don't know how to do is commented by "TODO" towards the end.
Please help!
import {
externalSchematic,
Rule,
SchematicContext,
Tree,
chain,
} from '#angular-devkit/schematics';
export function page(options: any): Rule {
const name = options.name;
return chain([
// create module for this page
externalSchematic('#schematics/angular', 'module', {
name: `pages/${name}`,
routing: true
}),
// create simple component to display in this page
externalSchematic('#schematics/angular', 'component', {
name: `pages/${name}/${name}`,
routing: true
}),
// add component to routing module
(tree: Tree, _context: SchematicContext) => {
// TODO 1: import new component into routing module
// e.g. import { MyPageComponent } from "./my-page/my-page.component";
// TODO 2: add component to routes
// const routes: Routes = [{ path: '', pathMatch: 'full', component: MyPageComponent }];
return tree;
},
]);
}

Resources