How to mock callbacks with mockito correctly? - spring

I have a method like this:
override fun functionToBeMocked(
param: Param,
onSuccess: (param: Param) -> Unit,
onError: (param: Param, errorMessage: String) -> Unit
) {
val response = factory.request(param)
.exchange()
.block()
if (response?.statusCode()?.is2xxSuccessful == true {
onSuccess(param)
} else if (response?.statusCode()?.isError == true) {
val status = response.rawStatusCode()
val body = response.bodyToMono(String::class.java).block()
val errorMessage = "$status : $body"
onError(param, errorMessage)
} else {
return
}
}
I want to test the service which calls this method with the given onSuccess and onError functions. How can I mock functionToBeMocked() to just return onSuccess(param) or onError(param)?
Current test:
#Test
fun test() {
val failure = ParamDoc(param.id, param)
whenever(repo.findAll()).thenReturn(listOf(failure))
// This method call should just execute onSuccess() or onError depending on the testcase
// whenever(mockedService.functionToBeMocked).thenAnswer.. (?)
underTest.functionToBeTested()
// verify(..)
}
Update: request function in factory:
fun request(param: Param): WebClient.RequestHeadersSpec<*> {
val client = WebClient
.builder()
.defaultHeader("API-KEY", config.apiKey)
.baseUrl(config.baseUrl)
.build()
return client
.post()
.uri("/service/test")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Dto.fromParam(param))
}

Related

Webclient retryWhen and onErrorResume mutually exclusive?

I am trying to implement a retry on specific exception but I could not make it work using the following:
return client
.sendWebhook(request, url)
.exchangeToMono(
response -> {
final HttpStatus status = response.statusCode();
return response
.bodyToMono(String.class)
.defaultIfEmpty(StringUtils.EMPTY)
.map(
body -> {
if (status.is2xxSuccessful()) {
log.info("HTTP_SUCCESS[{}][{}] body[{}]", functionName, company, body);
return ResponseEntity.ok().body(body);
} else {
log.warn(
format(
"HTTP_ERROR[%s][%s] status[%s] body[%s]",
functionName, company, status, body));
return status.is4xxClientError()
? ResponseEntity.badRequest().body(body)
: ResponseEntity.internalServerError().body(body);
}
});
})
.retryWhen(
Retry.backoff(1, Duration.ofSeconds(1))
.filter(
err -> {
if (err instanceof PrematureCloseException) {
log.warn("PrematureCloseException detected retrying.");
return true;
}
return false;
}))
.onErrorResume(
ex -> {
log.warn(
format(
"HTTP_ERROR[%s][%s] errorInternal[%s]",
functionName, company, ex.getMessage()));
return Mono.just(ResponseEntity.internalServerError().body(ex.getMessage()));
});
It seems that the retry is never getting called on PrematureCloseException.
Resolved, it was not working because of rootCause
Retry.backoff(3, Duration.ofMillis(500))
.filter(
ex -> {
if (ExceptionUtils.getRootCause(ex) instanceof PrematureCloseException) {
log.info(
"HTTP_RETRY[{}][{}] PrematureClose detected retrying", functionName, company);
return true;
}
return false;
});

NestJS/GraphQL/Passport - getting unauthorised error from guard

I'm trying to follow along with this tutorial and I'm struggling to convert the implementation to GraphQL.
local.strategy.ts
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authenticationService: AuthenticationService) {
super();
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authenticationService.getAuthenticatedUser(
email,
password,
);
if (!user) throw new UnauthorizedException();
return user;
}
}
local.guard.ts
#Injectable()
export class LogInWithCredentialsGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
req.body = ctx.getArgs();
await super.canActivate(new ExecutionContextHost([req]));
await super.logIn(req);
return true;
}
}
authentication.type.ts
#InputType()
export class AuthenticationInput {
#Field()
email: string;
#Field()
password: string;
}
authentication.resolver.ts
#UseGuards(LogInWithCredentialsGuard)
#Mutation(() => User, { nullable: true })
logIn(
#Args('variables')
_authenticationInput: AuthenticationInput,
#Context() req: any,
) {
return req.user;
}
mutation
mutation {
logIn(variables: {
email: "email#email.com",
password: "123123"
} ) {
id
email
}
}
Even the above credentials are correct, I'm receiving an unauthorized error.
The problem is in your LogInWithCredentialsGuard.
You shouldn't override canAcitavte method, all you have to do is update the request with proper GraphQL args because in case of API request, Passport automatically gets your credentials from req.body. With GraphQL, execution context is different, so you have to manually set your args in req.body. For that, getRequest method is used.
As the execution context of GraphQL and REST APIs is not same, you have to make sure your guard works in both cases whether it's controller or mutation.
here is a working code snippet
#Injectable()
export class LogInWithCredentialsGuard extends AuthGuard('local') {
// Override this method so it can be used in graphql
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const gqlReq = ctx.getContext().req;
if (gqlReq) {
const { variables } = ctx.getArgs();
gqlReq.body = variables;
return gqlReq;
}
return context.switchToHttp().getRequest();
}
}
and your mutation will be like
#UseGuards(LogInWithCredentialsGuard)
#Mutation(() => User, { nullable: true })
logIn(
#Args('variables')
_authenticationInput: AuthenticationInput,
#Context() context: any, // <----------- it's not request
) {
return context.req.user;
}
I've been able to get a successful login with a guard like this:
#Injectable()
export class LocalGqlAuthGuard extends AuthGuard('local') {
constructor() {
super();
}
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const req = ctx.getContext().req;
req.body = ctx.getArgs();
return req;
}
async canActivate(context: ExecutionContext) {
await super.canActivate(context);
const ctx = GqlExecutionContext.create(context);
const req = ctx.getContext().req;
await super.logIn(req);
return true;
}
}

NestJS Context is undefined in graphql subscription

can someone help me, why is the CONTEXT undefined inside my subscription?
#Subscription(returns => CommentsDto, {
filter: (payload, variables, context) => {
console.log({ payload, variables, context }) // <------------ context context undefined
const isSameCode = variables.code === payload.newComment.code
const isAuthorized = context.req.headers.clientauthorization === payload.clientauthorization
return isSameCode && isAuthorized
},
})
newComment(
#Context() context,
#Args(({ name: 'code', type: () => String })) code: string,
) {
console.log(context) // <------------ undefined
return this.publisherService.asyncIterator('newComment')
}
It is working for Queries and Mutatinos...
Graphql definition is:
const GraphQLDefinition = GraphQLModule.forRoot({
context: ({ req, connection }) => {
// subscriptions
if (connection) {
return { req: connection.context }
}
// queries and mutations
return { req }
},
installSubscriptionHandlers: true,
path: '/graphql',
playground: true,
})
Thank you for any help
Because the Req and Res are undefined in the case of subscriptions so when you try to log the context it is undefined.
For context to be available you need to change the guards that you are using to return the context which can be found in the connection variable.
Basically to summarize:
=> req, res used in http/query & mutations
=> connection used in webSockets/subscriptions
Now to get the context correctly you will have to perform these steps exactly:
Modify App module file to use the GraphqlModuleImport
Modify Extract User Guard and Auth guard (or whatever guards you are using)
to return data for both query/mutation and subscription case.
Receive data using the context in the subscription.
Add jwtTokenPayload extractor function in the Auth service.
Opitonal: Helper Functions and DTOs for Typescript.
1-Detail:
GraphQLModule.forRootAsync({
//import AuthModule for JWT headers at graphql subscriptions
imports: [AuthModule],
//inject Auth Service
inject: [AuthService],
useFactory: async (authService: AuthService) => ({
debug: true,
playground: true,
installSubscriptionHandlers: true,
// pass the original req and res object into the graphql context,
// get context with decorator `#Context() { req, res, payload, connection }: GqlContext`
// req, res used in http/query&mutations, connection used in webSockets/subscriptions
context: ({ req, res, payload, connection }: GqlContext) => ({
req,
res,
payload,
connection,
}),
// subscriptions/webSockets authentication
typePaths: ["./**/*.graphql"],
resolvers: { ...resolvers },
subscriptions: {
// get headers
onConnect: (connectionParams: ConnectionParams) => {
// convert header keys to lowercase
const connectionParamsLowerKeys: Object = mapKeysToLowerCase(
connectionParams,
);
// get authToken from authorization header
let authToken: string | false = false;
const val = connectionParamsLowerKeys["authorization"];
if (val != null && typeof val === "string") {
authToken = val.split(" ")[1];
}
if (authToken) {
// verify authToken/getJwtPayLoad
const jwtPayload: JwtPayload = authService.getJwtPayLoad(
authToken,
);
// the user/jwtPayload object found will be available as context.currentUser/jwtPayload in your GraphQL resolvers
return {
currentUser: jwtPayload.username,
jwtPayload,
headers: connectionParamsLowerKeys,
};
}
throw new AuthenticationError("authToken must be provided");
},
},
definitions: {
path: join(process.cwd(), "src/graphql.classes.ts"),
outputAs: "class",
},
}),
}),
2-Detail:
My getRequest function example from the ExtractUserGuard class that extends the AuthGuard(jwt) class.
Change from:
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext().req;
return request;}
to this:
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
// req used in http queries and mutations, connection is used in websocket subscription connections, check AppModule
const { req, connection } = ctx.getContext();
// if subscriptions/webSockets, let it pass headers from connection.context to passport-jwt
const requestData =
connection && connection.context && connection.context.headers
? connection.context
: req;
return requestData;
}
3- Now you can get this data in your resolver.
#Subscription("testSubscription")
#UseGuards(ExtractUserGuard)
async testSubscription(
#Context("connection") connection: any,
): Promise<JSONObject> {
const subTopic = `${Subscriptions_Test_Event}.${connection.context.jwtPayload.email}`;
console.log("Listening to the event:", subTopic);
return this.pubSub.asyncIterator(subTopic);
}
4- For getting the jwtPayload using the token add the following function to your AuthService.
getJwtPayLoad(token: string): JwtPayload {
const jwtPayload = this.jwtService.decode(token);
return jwtPayload as JwtPayload;
}
5-Helper Functions and DTOs example (that I used in my project)
DTOs:
export interface JwtPayload {
username?: string;
expiration?: Date;
}
export interface GqlContext {
req: Request;
res: Response;
payload?: JwtPayload;
// required for subscription
connection: any;
}
export interface ConnectionParams {
authorization: string;
}
Helper Function:
export function mapKeysToLowerCase(
inputObject: Record<string, any>,
): Record<string, any> {
let key;
const keys = Object.keys(inputObject);
let n = keys.length;
const newobj: Record<string, any> = {};
while (n--) {
key = keys[n];
newobj[key.toLowerCase()] = inputObject[key];
}
return newobj;
}

Spring webClient generate NullPointException after going through the filter

I want to do exception handling. When StatusCode 200, but not the desired body.
To make the processing global I am using filter.
below is the code.
public WebClient webClient() {
return WebClient.builder()
.filter(ExchangeFilterFunction.ofResponseProcessor(response -> {
// statusCode 400 || 500
if (response.statusCode().isError()) {
return response.bodyToMono(String.class)
.flatMap(body ->
Mono.error(new ExternalClientException(body))
)
);
}
// statusCode 200
return response.bodyToMono(String.class)
.flatMap(body -> {
if (YanoljaConstants.EMPTY_BODY.contains(body)) {
return Mono.error(new ExternalClientException(body))
.cast(ClientResponse.class);
}
return Mono.just(response); // normal
});
}))
.build();
}
And the actual client code is shown below.
public Foo getPlace(int no) {
return Objects.requireNonNull(contentsApiV2Client.get()
.uri(uriBuilder -> uriBuilder.path("/path").build(no))
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Foo>() {
})
.block());
}
When using this, NullPointerException occurs when responding normally after going through the filter(return Mono#just(response)).
I want clear this issue.
For reference, the below code works normally.
public WebClient webClient() {
return WebClient.builder()
.filter(ExchangeFilterFunction.ofResponseProcessor(response -> {
// statusCode 400 || 500
if (response.statusCode().isError()) {
return response.bodyToMono(String.class)
.flatMap(body ->
Mono.error(new ExternalClientException(body))
)
);
}
return Mono.just(response); // normal
}))
.build();
}
Thank you. waiting for your answer!
Solve!
public WebClient webClient() {
return WebClient.builder()
.filter(ExchangeFilterFunction.ofResponseProcessor(response -> {
// statusCode 400 || 500
if (response.statusCode().isError()) {
return response.bodyToMono(String.class)
.flatMap(body ->
Mono.error(new ExternalClientException(body))
)
);
}
// statusCode 200
return response.bodyToMono(String.class)
.flatMap(body -> {
if (YanoljaConstants.EMPTY_BODY.contains(body)) {
return Mono.error(new ExternalClientException(body))
.cast(ClientResponse.class);
}
return Mono.just(ClientResponse.create(HttpStatus.OK)
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(body)
.build()); // normal
});
}))
.build();
}
I realized that running the monoTobody method consumes it and the value disappears. So I solved it by creating a new ClientResponse.
Thank you.

How to observe the angular 5 interceptor error in some component

Hi I am new to angular 5 and followed some blogs to write the HTTP Interceptor.
export class AngularInterceptor implements HttpInterceptor {
public http404 = false;
constructor() { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log("intercepted request ... ");
// Clone the request to add the new header.
const httpReq = req.clone(
{
headers: req.headers.set("headerName", "headerValue")
}
);
console.log("Sending request with new header now ...");
//send the newly created request
return next.handle(httpReq)
.catch((error, caught) => {
//intercept the respons error and displace it to the console
console.log("Error Occurred");
if(error.status === 404)
this.http404 = true;
//need to pass this value to another component. Let's say app.component.ts and display some message to the user.
//return the error to the method that called it
return Observable.throw(error);
}) as any;
}
}
This is working fine. But what I need to do is to pass this error code to other components and print out a message on the screen for the user. One wy to do that is to create an observable but I am unable to implement that.
Any help is highly appreciated.
You can use a service to do that, by leveraging a Subject. Here's an example of using BehaviourSubject.
First you create a service. This service will be shared across the two classes:
export class BroadcastService {
public http404: BehaviorSubject<boolean>;
constructor() {
//initialize it to false
this.http404 = new BehaviorSubject<boolean>(false);
}
}
In your HttpInterceptor class, you inject the BroadcastService into it. To update the BehvaiourSubject, simply use .next():
export class AngularInterceptor implements HttpInterceptor {
public http404 = false;
constructor(public broadcastService: BroadcastService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log("intercepted request ... ");
// Clone the request to add the new header.
const httpReq = req.clone({
headers: req.headers.set("headerName", "headerValue")
});
console.log("Sending request with new header now ...");
//send the newly created request
return next.handle(httpReq)
.catch((error, caught) => {
//intercept the respons error and displace it to the console
console.log("Error Occurred");
if (error.status === 404)
this.http404 = true;
//need to pass this value to another component. Let's say app.component.ts and display some message to the user.
this.broadcastService.http404.next(true);
//return the error to the method that called it
return Observable.throw(error);
}) as any;
}
}
And in your app.component.ts, simply subscribe it using .asObservable(). You need to inject it too:
export class AppComponent implements ngOnInit {
constructor(public broadCastService: BroadcastService) {
}
OnInit() {
this.broadCastService.http404.asObservable().subscribe(values => {
console.log(values); // will return false if http error
});
}
}

Resources