How to send raw data as payload with MQTT on Nestjs - nestjs

I want to send just raw data to the broker but the string is sent as string with quotations.
i.e.
the message I want to sent is: ti=0F:0000000000&id=E8EB1BE99345
but what is sent is: "ti=0F:0000000000&id=E8EB1BE99345"
How can I achieve that?
I have declared serializer in the mqtt client like below,
#Module({
imports: [
ClientsModule.register([
{
name: 'MQTT_CLIENT',
transport: Transport.MQTT,
options: {
url: 'mqtt://XX.XXX.XXX.XXX:1883',
clientId: 'my-client-id',
serializer: {
serialize: (value: any) => value.data,
},
},
},
]),
ConfigModule.forRoot(),
],
controllers: [AppController],
})
export class AppModule {}
You can see here that the ACKs are sent with the quotations, and they should be sent like above, without them:

Apparently this behaviour is something standard in Nestjs, the publishing response will be returned as the result object of applying JSON.stringify().
There's no way of configure it anyhow.
The only option for doing that is to create a custom ClientProxy or even easier extend from ClientMqtt and then override the publish method, so all the logic related with serialization is ignored, and just publish the raw data of the packet.
Custom class with the modified publish method:
import { ClientMqtt, ReadPacket, WritePacket } from '#nestjs/microservices';
export class RawPayloadProxy extends ClientMqtt {
protected publish(
partialPacket: ReadPacket,
callback: (packet: WritePacket) => any,
): () => void {
try {
const pattern = this.normalizePattern(partialPacket.pattern);
const responseChannel = this.getResponsePattern(pattern);
let subscriptionsCount =
this.subscriptionsCount.get(responseChannel) || 0;
const publishPacket = () => {
subscriptionsCount = this.subscriptionsCount.get(responseChannel) || 0;
this.subscriptionsCount.set(responseChannel, subscriptionsCount + 1);
this.mqttClient.publish(
this.getRequestPattern(pattern),
partialPacket.data,
);
};
if (subscriptionsCount <= 0) {
this.mqttClient.subscribe(
responseChannel,
(err: any) => !err && publishPacket(),
);
} else {
publishPacket();
}
return () => {
this.unsubscribeFromChannel(responseChannel);
};
} catch (err) {
callback({ err });
}
}
}
Definition of the custom client class on AppModule:
#Module({
imports: [
ClientsModule.register([
{
name: 'MQTT_CLIENT',
customClass: RawPayloadProxy,
options: {
url: 'mqtt://XX.XXX.XXX.XXX:1883',
clientId: 'client-xxx',
},
},
]),
],
controllers: [AppController],
})
export class AppModule {}

Related

How would I mock the DataSource from TypeORM in NestJS Jest tests?

I am trying to write tests for a small project in NestJS. Here is the relevant code for context:
dummy.controller.ts
#Controller(UrlConstants.BASE_URL + 'dummy')
export class DummyContoller {
constructor(
private readonly sessionService: SessionService,
) { }
#Get('validateSession')
async checkValidateSession(#Query('sessionId') sessionId: string) {
const session = await this.sessionService.validateSession(sessionId);
console.log(session);
return { message: "OK" };
}
}
session.service.ts
#Injectable()
export class SessionService {
constructor(
private readonly sessionRepo: SessionRepository,
private readonly accountRepo: AccountRepository
) { }
#WithErrorBoundary(AuthCodes.UNKNOWN_LOGIN_ERROR)
async validateSession(sessionId: string) {
const session = await this.sessionRepo.findOneBy({ sessionId });
if (!session || this.isSessionExpired(session)) {
session && await this.sessionRepo.remove(session);
throw new HttpException({
code: AuthCodes.SESSION_TIMEOUT,
message: AuthMessages.SESSION_TIMEOUT
}, HttpStatus.UNAUTHORIZED)
}
return session;
}
...
}
session.repository.ts (Any repository)
#Injectable()
export class SessionRepository extends Repository<Session> {
constructor(private dataSource: DataSource) {
super(Session, dataSource.createEntityManager())
}
...
}
This is how I wrote my test (this is my first time writing a test using Jest and I am not really experienced in writing tests in general):
describe('DummyController', () => {
let dummyContoller: DummyContoller;
let sessionService: SessionService;
let sessionRepo: SessionRepository;
let accountRepo: AccountRepository;
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [DummyContoller],
providers: [SessionService, SessionRepository, AccountRepository]
}).compile();
dummyContoller = module.get<DummyContoller>(DummyContoller);
sessionService = module.get<SessionService>(SessionService);
sessionRepo = module.get<SessionRepository>(SessionRepository);
accountRepo = module.get<AccountRepository>(AccountRepository);
})
describe('checkValidateSession', () => {
it('should return valid session', async () => {
const sessionId = "sessionId1";
const session = new Session();
jest.spyOn(sessionService, 'validateSession').mockImplementation(async (sessionId) => session);
expect(await dummyContoller.checkValidateSession(sessionId)).toBe(session);
})
})
})
Upon running the test, I encounter:
Nest can't resolve dependencies of the SessionRepository (?). Please make sure that the argument DataSource at index [0] is available in the RootTestModule context.
Potential solutions:
- If DataSource is a provider, is it part of the current RootTestModule?
- If DataSource is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing DataSource */ ]
})
I looked this problem and I came across a number of solutions but most of them had #InjectRepository() instead of creating a separate Repository class where they would provide getRepositoryToken() and then use a mock factory [Link]. I couldn't find a way to make this work.
Another solution suggested using an in-memory database solution [Link]. But this felt more like a hack rather than a solution.
How can I test the above setup?
Based on this comment, I was able to get this working by using the following in the providers in the test:
providers: [
SessionService,
{ provide: SessionRepository, useClass: SessionMockRepository },
]
SessionMockRepository contains a mocked version of all additional functions in that particular repository:
export class SessionMockRepository extends Repository<Session> {
someFunction = async () => jest.fn();
}
Currently, this works for me so I am accepting this. I am still open to more answers if there is a better way to do this.

How to make class-validator to stop on error?

I want class validator to stop validation as soon as the first error is found.
I know there is a stopAtFirstError option, but that prevents further validation on property level only. I want to stop it globally:
#IsString() // Let's say this received invalid value
someStr: string
#IsInt() // I want this to be NOT executed
someNuber: number
Is this possible?
Since there is no official support for this, I implemented a hacky way to make it work. Although it doesn't stop the validation, only returns the message from initial one & ignores the rest.
ValidationErrorException
// validation-error.exception.ts
import { HttpException, HttpStatus } from "#nestjs/common";
export class ValidationErrorException extends HttpException {
data: any;
constructor(data: any) {
super('ValidationError', HttpStatus.BAD_REQUEST);
this.data = data
}
getData(){
return this.data;
}
}
Use Exception Factory to return the exception
// main.ts
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
stopAtFirstError: true,
exceptionFactory: (errors: ValidationError[]) => {
return new ValidationErrorException(errors);
},
}),
);
Validation Error Filter
// validation-error.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost } from '#nestjs/common';
import { Response } from 'express';
import { ValidationErrorException } from './validation-error.exception';
#Catch(ValidationErrorException)
export class ValidationErrorFilter implements ExceptionFilter {
catch(exception: ValidationErrorException, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse<Response>();
const status = exception.getStatus();
let constraints = exception.getData()[0].constraints
response
.status(status)
.json({
statusCode: status,
message: constraints[Object.keys(constraints)[0]],
error: exception.message
});
}
}
Use The filter
// app.module.ts
providers: [
{
provide: APP_FILTER,
useClass: ValidationErrorFilter,
},
],

NestJS v9: implement durable providers

[SOLVED] I'm pretty new to NestJS and trying to get my head around durable providers but i can't get them to work.
My scenario is that i have a service with some logic and two providers that implement the same interface to get some data. Depending on a custom header value i want to use Provider1 or Provider2 and the service itself does not have to know about the existing provider implementations.
Since i'm in a request scoped scenario but i know there are only 2 possible dependency-subtrees i want to use durable providers that the dependencies are not newly initialised for each request but reused instead.
I set up the ContextIdStrategy as described in the official docs and it is executed on each request but i miss the part how to connect my provider implementations with the ContextSubtreeIds created in the ContextIdStrategy.
Interface:
export abstract class ITest {
abstract getData(): string;
}
Implementations:
export class Test1Provider implements ITest {
getData() {
return "TEST1";
}
}
export class Test2Provider implements ITest {
getData() {
return "TEST2";
}
}
Service:
#Injectable()
export class AppService {
constructor(private readonly testProvider: ITest) {}
getHello(): string {
return this.testProvider.getData();
}
}
Controller:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getData(): string {
return this.appService.getData();
}
}
ContextIdStrategy:
const providers = new Map<string, ContextId>([
["provider1", ContextIdFactory.create()],
["provider2", ContextIdFactory.create()],
]);
export class AggregateByProviderContextIdStrategy implements ContextIdStrategy {
attach(contextId: ContextId, request: Request) {
const providerId = request.headers["x-provider-id"] as string;
let providerSubTreeId: ContextId;
if (providerId == "provider1") {
providerSubTreeId = providers["provider1"];
} else if (providerId == "provider2") {
providerSubTreeId = providers["provider2"];
} else {
throw Error(`x-provider-id ${providerId} not supported`);
}
// If tree is not durable, return the original "contextId" object
return (info: HostComponentInfo) =>
info.isTreeDurable ? providerSubTreeId : contextId;
}
}
Main:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
ContextIdFactory.apply(new AggregateByProviderContextIdStrategy());
await app.listen(3000);
}
bootstrap();
Module:
#Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: ITest,
useFactory: () => {
// THIS IS THE MISSING PIECE.
// Return either Test1Provider or Test2Provider based on the ContextSubtreeId
// which is created by the ContextIdStrategy
return new Test1Provider();
},
},
AppService,
],
})
export class AppModule {}
The missing part was a modification of the ContextIdStrategy return statement:
return {
resolve: (info: HostComponentInfo) => {
const context = info.isTreeDurable ? providerSubTreeId : contextId;
return context;
},
payload: { providerId },
}
after that change, the request object can be injected in the module and where it will only contain the providerId property and based on that, the useFactory statement can return different implementations

Create mocked service (object) with one method returning a value

In an Angular environment, how can I very easily create in a Jest environment a mocked service for a service object returning a specific value? This could be via Jest of ng-mocks, etc.
An oversimplified example:
// beforeEach:
// setup an Angular component with a service component myMockService
// Test 1:
// fake "myMockService.doSomething" to return value 10
// expect(myComponent.getBalance()).toEqual( "Your balance: 10");
// Test 2:
// fake "myMockService.doSomething" to return value 20
// expect(myComponent.getBalance()).toEqual( "Your balance: 20");
I have studied the Jest and ng-mocks docs but didn't find a very easy approach. Below you find 2 working approaches. Can you improve the version?
My simplified Angular component:
#Component({
selector: 'app-servicecounter',
templateUrl: './servicecounter.component.html'
})
export class ServicecounterComponent {
private myValue: number = 1;
constructor(private counterService: CounterService) { }
public doSomething(): void {
// ...
myValue = this.counterService.getCount();
}
}
This is the simplified service:
#Injectable()
export class CounterService {
private count = 0;
constructor() { }
public getCount(): Observable<number> {
return this.count;
}
public increment(): void {
this.count++;
}
public decrement(): void {
this.count--;
}
public reset(newCount: number): void {
this.count = newCount;
}
}
Try 1: a working solution: with 'jest.genMockFromModule'.
The disadvantage is that I can only create a returnValue only at the start of each series of tests, so at beforeEach setup time.
beforeEach(async () => {
mockCounterService = jest.genMockFromModule( './counterservice.service');
mockCounterService.getCount = jest.fn( () => 3);
mockCounterService.reset = jest.fn(); // it was called, I had to mock.fn it.
await TestBed.configureTestingModule({
declarations: [ServicecounterComponent],
providers: [ { provide: CounterService, useValue: mockCounterService }],
}).compileComponents();
fixture = TestBed.createComponent(ServicecounterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('shows the count', () => {
setFieldValue(fixture, 'reset-input', String(currentCount));
click(fixture, 'reset-button');
expect(mockCounterService.getCount()).toEqual( 3);
expect( mockCounterService.getCount).toBeCalled();
});
Try 2: replace 'jest.genMockFromModule' with 'jest.createMockFromModule': works equally well.
The disadvantage is still that I can create a returnValue only at the start of each series of tests, so at beforeEach setup time.
Try 3: create a mock object upfront: didn't work
jest.mock( "./counterservice.service");
beforeEach(async () => {
// Create fake
mockCounterService = new CounterService();
(mockCounterService.getCount() as jest.Mock).mockReturnValue( 0);
await TestBed.configureTestingModule({
declarations: [ServicecounterComponent],
providers: [{ provide: CounterService, useValue: mockCounterService }],
}).compileComponents();
fixture = TestBed.createComponent(ServicecounterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('shows the count', () => {
// do something that will trigger the mockCountService getCount method.
expect(mockCounterService.getCount).toEqual( 0);
});
This doesn't work, giving the error:
> (mockCounterService.getCount() as jest.Mock).mockReturnValue( 0);
> Cannot read property 'mockReturnValue' of undefined
Try 4: with .fn(). The disadvantage is that the original class may change, then the test object MUST change.
beforeEach(async () => {
mockCounterService = {
getCount: jest.fn().mockReturnValue( 0),
increment: jest.fn,
decrement: jest.fn(),
reset: jest.fn
};
await TestBed.configureTestingModule({
declarations: [ServicecounterComponent],
providers: [{ provide: CounterService, useValue: mockCounterService }],
}).compileComponents();
});
it( '... ', () => {
// ...
expect(mockCounterService.reset).toHaveBeenCalled();
});
This time, the error is:
> Matcher error: received value must be a mock or spy function ...
> expect(mockCounterService.reset).toHaveBeenCalled();
Can you help improving this way of working?
You need to use MockBuilder to mock the service, and MockInstance to customize it.
Also getCount is an observable, therefore its mock should return Subject, which we can manipulate.
// to reset MockInstance customizations after tests
MockInstance.scope();
// to use jest.fn on all mocks https://ng-mocks.sudo.eu/extra/auto-spy
beforeEach(() => ngMocks.autoSpy('jest'));
afterEach(() => ngMocks.autoSpy('reset'));
beforeEach(() => MockBuilder(ServicecounterComponent, CounterService));
it('testing', () => {
// this is our control of observable of getCount
const getCount$ = new Subject<number>();
// now we need to return it when getCount is called
const getCount = MockInstance(CounterService, 'getCount', jest.fn())
.mockReturnValue(getCount$);
// now we can use it in our test.
const fixture = MockRender(ServicecounterComponent);
ngMocks.click('.reset-button');
expect(getCount).toHaveBeenCalled();
getCount$.next(3);
expect(ngMocks.formatText(fixture)).toContain('3');
});

Getting gRPC Metadata in NestJS from Client

I would like to be able to read gRPC metadata when making a unary call with the gRPC client.
Currently this is already possible with StreamCalls' which is also properly documented, but I can't figure it out with Unary Call's
Consider the following code:
Simplified Provider:
{
provide: 'SERVICE_GRPC',
useFactory: () => {
return ClientProxyFactory.create({
transport: Transport.GRPC,
options: {
package: 'myPackage',
protoPath: 'my.proto
url: '127.0.0.1',
},
});
}
},
Simplified Class
interface SomeService {
someGrpcMethod: (argument: Request) => Observable<Response>;
}
class SomeClass {
constructor(#Inject('SERVICE_GRPC') private readonly _client: ClientGrpc) {}
onModuleInit(): void {
this._someService = this._client.getService<SomeService>('SomeService');
}
someMethod(): void {
const observer = this._someService.someGrpcMethod({param1:true})
// Need the metadata send by server here
observer.subscribe(...);
}
}
Does anyone know how to do this?

Resources