How do I make a Task with two parameters? - serenity-js

In the Serenity-js book we have one example of a Task with just one parameter :
// spec/screenplay/tasks/add_a_todo_item.ts
import { PerformsTasks, Task } from 'serenity-js/protractor';
export class AddATodoItem implements Task {
static called(itemName: string) { // static method to improve the readability
return new AddATodoItem(itemName);
}
performAs(actor: PerformsTasks): PromiseLike<void> { // required by the Task interface
return actor.attemptsTo( // delegates the work to lower-level tasks
// todo: interact with the UI
);
}
constructor(private itemName: string) { // constructor assigning the name of the item
// to a private field
}
Imagine you can add a date the TodoItem should be done. We would receive a date parameter, say 'deadline'. I cannot figure out how to do it.
First thoughts:
constructor:
constructor(private itemName: string, private deadline: Date) {
}
performAs: just add the interaction to type the deadline
We would have a second static method.
And possibly the called method return would be changed.
Thanks for your explanations.

There are several ways to do it, depending on which parameters are mandatory, and which are optional, and how many of them you'd like the task to have.
No parameters
If you have a task with no parameters, the easier way to define it is using the Task.where factory function:
import { Task } from '#serenity-js/core';
const Login = () => Task.where(`#actor logs in`,
Click.on(SubmitButton),
);
This is almost the same as using a class-style definition below, but with much less code:
class Login extends Task {
performAs(actor: PerformsTasks) {
return actor.attemptsTo(
Click.on(SubmitButton),
);
}
toString() {
return `#actor logs in`;
}
}
One parameter
You can use the above approach with tasks that should receive one parameter:
const LoginAs = (username: string) => Task.where(`#actor logs in as ${ username }`,
Enter.theValue(username).into(UsernameField),
Click.on(SubmitButton),
);
Which, alternatively, you could also implement as follows:
const Login = {
as: (username: string) => Task.where(`#actor logs in as ${ username }`,
Enter.theValue(username).into(UsernameField),
Click.on(SubmitButton),
),
}
I find this second version a bit more elegant and more consistent with the built-in interactions like Click.on, Enter.theValue, etc. since you'd be calling Login.as rather than LoginAs in your actor flow.
N parameters
If there are more than 1 parameters, but all of them are required and you're simply after an elegant DSL, you could extend the above pattern as follows:
const Login = {
as: (username: string) => ({
identifiedBy: (password: string) => Task.where(`#actor logs in as ${ username }`,
Enter.theValue(username).into(...),
Enter.theValue(password).into(...),
Click.on(SubmitButton),
}),
}
You'd then invoke the above task:
actor.attemptsTo(
Login.as(username).identifiedBy(password),
);
This design is not particularly flexible, as it doesn't allow you to change the order of parameters (i.e. you can't say Login.identifiedBy(password).as(username)) or make some of the parameters optional, but gives you a good-looking DSL with relatively little implementation effort.
More flexibility
If you require more flexibility, for example in a scenario where some parameters are optional, you might opt for the class-style definition and a quasi-builder pattern. (I say "quasi" because it doesn't mutate the object, but instead produces new objects).
For example, let's assume that while the system required the username to be provided, the password might be optional.
class Login extends Task {
static as(username: string) {
return new Login(username);
}
identifiedBy(password: string {
return new Login(this.username, password);
}
constructor(
private readonly username: string,
private readonly password: string = '',
) {
super();
}
performAs(actor: PerformsTasks) {
return actor.attemptsTo(
Enter.theValue(username).into(...),
Enter.theValue(password).into(...),
Click.on(SubmitButton),
);
}
toString() {
return `#actor logs in as ${ this.username }`;
}
}
You can, of course, take it even further and decouple the act of instantiating the task from the task itself, which is useful if the different tasks are different enough to justify separate implementations:
export class Login {
static as(username: string) {
return new LoginWithUsernameOnly(username);
}
}
class LoginWithUsernameOnly extends Task {
constructor(
private readonly username: string,
) {
super();
}
identifiedBy(password: string {
return new LoginWithUsernameAndPassword(this.username, password);
}
performAs(actor: PerformsTasks) {
return actor.attemptsTo(
Enter.theValue(username).into(...),
Click.on(SubmitButton),
);
}
toString() {
return `#actor logs in as ${ this.username }`;
}
}
class LoginWithUsernameAndPassword extends Task {
constructor(
private readonly username: string,
private readonly username: string,
) {
super();
}
performAs(actor: PerformsTasks) {
return actor.attemptsTo(
Enter.theValue(this.username).into(...),
Enter.theValue(this.password).into(...),
Click.on(SubmitButton),
);
}
toString() {
return `#actor logs in as ${ this.username }`;
}
}
Both the above implementations allow you to call the task as Login.as(username) and Login.as(username).identifiedBy(password), but while the first implementation uses a default value of an empty string for the password, the second implementation doesn't even touch the password field.
I hope this helps!
Jan

Related

NestJS lifecycle methods invoked without implementing their interface

I am having a small question about NestJS. In my code, there is a service which looks something like:
`
import { Inject, Injectable } from '#nestjs/common';
import neo4j, { Driver, int, Result, Transaction } from 'neo4j-driver';
import { Neo4jConfig } from './neo4j-config.interface';
import { NEO4J_CONFIG, NEO4J_DRIVER } from './neo4j.constants';
#Injectable()
export class Neo4jService {
constructor(
#Inject(NEO4J_CONFIG) private readonly config: Neo4jConfig,
#Inject(NEO4J_DRIVER) private readonly driver: Driver,
) {}
onApplicationBootstrap() {
console.log('Hello');
}
getDriver(): Driver {
return this.driver;
}
getConfig(): Neo4jConfig {
return this.config;
}
int(value: number) {
return int(value);
}
beginTransaction(database?: string): Transaction {
const session = this.getWriteSession(database);
return session.beginTransaction();
}
getReadSession(database?: string) {
return this.driver.session({
database: database || this.config.database,
defaultAccessMode: neo4j.session.READ,
});
}
getWriteSession(database?: string) {
return this.driver.session({
database: database || this.config.database,
defaultAccessMode: neo4j.session.WRITE,
});
}
read(
cypher: string,
params?: Record<string, unknown>,
databaseOrTransaction?: string | Transaction,
): Result {
if (databaseOrTransaction instanceof Transaction) {
return (<Transaction>databaseOrTransaction).run(cypher, params);
}
const session = this.getReadSession(<string>databaseOrTransaction);
return session.run(cypher, params);
}
write(
cypher: string,
params?: Record<string, unknown>,
databaseOrTransaction?: string | Transaction,
): Result {
if (databaseOrTransaction instanceof Transaction) {
return (<Transaction>databaseOrTransaction).run(cypher, params);
}
const session = this.getWriteSession(<string>databaseOrTransaction);
return session.run(cypher, params);
}
private onApplicationShutdown() {
console.log('Goodbye')
return this.driver.close();
}
}
`
Then in my main.ts file I have this method called:
`
await app.listen(port);
`
As you can see my service does not implement neither onApplicationBootstrap nor onApplicationShutdown.
How does it come that those methods still get invoked? Should I implement onApplicationBootstrap and onApplicationShutdown or not?
As you can also see I' d like that my onApplicationBootstrap is a private method which would not be possible if I implement the interface.
So, I would like to ask you:
Why the two lifecycle methods get called event without implementing the interface?
Should I implement those interfaces at all or just go on and use the methods which would allow me to define them as private?
I expected those methods to not work without implementing the interfaces
The Typescript interface is there to help us as devs. It doesn't exist at runtime, there's no information about it, so the only thing Nest can do is just check "Hey, does this class have the onModuleInit method?" If yes, add it to a list of classes to call onModuleInit. Do the same with the other lifecycle methods.
The interfaces aren't explicitly necessary, but they do give us devs a better idea of the class by just looking at the export class... line because we can see what is implemented/extended.

TypeScript - Repository pattern with Sequelize

I'm converting my Express API Template to TypeScript and I'm having some issues with the repositories.
With JavaScript, I would do something like this:
export default class BaseRepository {
async all() {
return this.model.findAll();
}
// other common methods
}
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository {
constructor() {
super();
this.model = User;
}
async findByEmail(email) {
return this.model.findOne({
where: {
email,
},
});
}
// other methods
Now, with TypeScript, the problem is that it doesn't know the type of this.model, and I can't pass a concrete model to BaseRepository, because, well, it is an abstraction. I've found that sequelize-typescript exports a ModelCtor which declares all the static model methods like findAll, create, etc., and I also could use another sequelize-typescript export which is Model to properly annotate the return type.
So, I ended up doing this:
import { Model, ModelCtor } from 'sequelize-typescript';
export default abstract class BaseRepository {
protected model: ModelCtor;
constructor(model: ModelCtor) {
this.model = model;
}
public async all(): Promise<Model[]> {
return this.model.findAll();
}
// other common methods
}
import { Model } from 'sequelize-typescript';
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository {
constructor() {
super(User);
}
public async findByEmail(email: string): Promise<Model | null> {
return this.model.findOne({
where: {
email,
},
});
}
// other methods
}
Ok, this works, TypeScript doesn't complain about methods like findOne or create not existing, but that generates another problem.
Now, for example, whenever I get a User from the repository, if I try to access one of its properties, like user.email, TypeScript will complain that this property does not exist. Of course, because the type Model does not know about the specifics of each model.
Ok, it's treason generics then.
Now BaseRepository uses a generic Model type which the methods also use:
export default abstract class BaseRepository<Model> {
public async all(): Promise<Model[]> {
return Model.findAll();
}
// other common methods
}
And the concrete classes pass the appropriate model to the generic type:
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository<User> {
public async findByEmail(email: string): Promise<User | null> {
return User.findOne({
where: {
email,
},
});
}
// other methods
}
Now IntelliSense lights up correctly, it shows both abstract and concrete classes methods and the model properties (e.g. user.email).
But, as you have imagined, that leads to more problems.
Inside BaseRepository, where the methods use the Model generic type, TypeScript complains that 'Model' only refers to a type, but is being used as a value here. Not only that, but TypeScript also doesn't know (again) that the static methods from the model exist, like findAll, create, etc.
Another problem is that in both abstract and concrete classes, as the methods don't use this anymore, ESLint expects the methods to be static: Expected 'this' to be used by class async method 'all'. Ok, I can just ignore this rule in the whole file and the error is gone. It would be even nicer to have all the methods set to static, so I don't have to instantiate the repository, but maybe I'm dreaming too much.
Worth mentioning that although I can just silence those errors with // #ts-ignore, when I execute this, it doesn't work: TypeError: Cannot read property 'create' of undefined\n at UserRepository.<anonymous>
I researched a lot, tried to make all methods static, but static methods can't reference the generic type (because it is considered an instance property), tried some workarounds, tried to pass the concrete model in the constructor of BaseRepository along with the class using the generic type, but nothing seems to work so far.
In case you want to check the code: https://github.com/andresilva-cc/express-api-template/tree/main/src/App/Repositories
EDIT:
Found this: Sequelize-Typescript typeof model
Ok, I removed some unnecessary code from that post and that kinda works:
import { Model } from 'sequelize-typescript';
export default abstract class BaseRepository<M extends Model> {
constructor(protected model: typeof Model) {}
public async all(attributes?: string[]): Promise<M[]> {
// Type 'Model<{}, {}>[]' is not assignable to type 'M[]'.
// Type 'Model<{}, {}>' is not assignable to type 'M'.
// 'Model<{}, {}>' is assignable to the constraint of type 'M', but 'M' could be instantiated with a different subtype of constraint 'Model<any, any>'.
return this.model.findAll({
attributes,
});
}
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository<User> {
constructor() {
super(User);
}
}
I mean, if I put some // #ts-ignore it at least executes, and IntelliSense lights up perfectly, but TypeScript complains.
We faced the same problem. The solution was to declare returning types with an interface that an abstract repository class implements.
Code for the interface:
export type RepoResult<M> = Promise<Result<M | undefined, RepoError | undefined>>;
export interface IRepo<M> {
save(model: M): RepoResult<M>;
findById(id: string): RepoResult<M>;
search(parameterName: string, parameterValue: string, sortBy: string, order: number, pageSize: number, pageNumber: number): RepoResult<M[]>;
getAll(): RepoResult<M[]>;
deleteById(id: string): RepoResult<M>;
findByIds(ids: string[]): RepoResult<M[]>;
deleteByIds(ids: string[]): RepoResult<any>;
};
Code for the abstract class:
export abstract class Repo<M extends sequelize.Model> implements IRepo<M> {
protected Model!: sequelize.ModelCtor<M>;
constructor(Model: sequelize.ModelCtor<M>) {
this.Model = Model;
}
public async save(doc: M) {
try {
const savedDoc = await doc.save();
return Result.ok(savedDoc);
} catch (ex: any) {
logger.error(ex);
return Result.fail(new RepoError(ex.message, 500));
}
}
public async findById(id: string) {
try {
const doc = await this.Model.findOne({where: {
id: id
}});
if (!doc) {
return Result.fail(new RepoError('Not found', 404));
}
return Result.ok(doc);
} catch (ex: any) {
return Result.fail(new RepoError(ex.message, 500));
}
}
}
Hope it helps. Have a nice day:)
EDIT:
Result is a class that looks like this:
export class Result<V, E> {
public isSuccess: boolean;
public isFailure: boolean;
private error: E;
private value: V;
private constructor(isSuccess: boolean, value: V, error: E) {
if (isSuccess && error) {
throw new Error('Successful result must not contain an error');
} else if (!isSuccess && value) {
throw new Error('Unsuccessful error must not contain a value');
}
this.isSuccess = isSuccess;
this.isFailure = !isSuccess;
this.value = value;
this.error = error;
}
public static ok<V>(value: V): Result<V, undefined> {
return new Result(true, value, undefined);
}
public static fail<E>(error: E): Result<undefined, E> {
return new Result(false, undefined, error);
}
public getError(): E {
if (this.isSuccess) {
throw new Error('Successful result does not contain an error');
}
return this.error;
}
public getValue(): V {
if (this.isFailure) {
throw new Error('Unsuccessful result does not contain a value');
}
return this.value;
}
}
RepoError class:
type RepoErrorCode = 404 | 500;
export class RepoError extends Error {
public code: RepoErrorCode;
constructor(message: string, code: RepoErrorCode) {
super(message);
this.code = code;
}
}
RepoResult type:
export type RepoResult<M> = Promise<Result<M | undefined, RepoError | undefined>>;
You can find more info on the pattern at the link below:
https://khalilstemmler.com/articles/enterprise-typescript-nodejs/functional-error-handling/

How to extend a class in typescript

My service is designed in nodejs.
Below is my scenario
i have two controllers, one will be extending the other. there is a static function in both the controllers where in a static variable will be assigned some value.
depending on the condition of the data, im trying the make a call to the respective controller so that the static variable gets a appropriate assigned value.
Note:
The below code is just a snippet to explain the scenario and not the actual code of the application. But the order / calling / controller structure of this code snippet is exactly same. Also the listOfDept variable will be having separate business logic in the checkStart function of firstController and secondController.
// firstController.ts
firstController implements IFirstController {
private static listOfDept: string[];
static checkStart(){
firstController.listOfDept = // my logic to fill this object
}
constructor (){}
}
getRelevantData(next: (error: string, response: any) => void): void {
var myObject = firstController.listOfDept;
this.myRepository.uniqueData(myObject, next);
}
}
firstController.checkStart();
export = firstController;
//ifirstController.ts
interface IFirstController {
getRelevantData(next: (error: string, response: any) => void): void;
}
// secondController.ts
secondController extends firstController implements iSecondController {
private static listOfDept: string[];
static checkStart(){
firstController.listOfDept = ["Computer Science"];
}
constructor (){
super();
}
}
secondController.checkStart();
export = secondController;
//isecondController.ts
interface ISecondController implements ifirstController{}
//Controller calling the getRelevantData function
//middlewareController
middlewareController implements IMiddlewareController {
constructor(private firstController: IFirstController, private secondController: ISecondController) {
}
getDepData(data: any, next: (error: string, response: any) => void): void {
if(data.url = "fromParent") {
// im expecting this to make a call to checkStart() of firstController
this.firstController.getRelevantData();
} else {
// im expecting this to make a call to checkStart() of secondController
this.secondController.getRelevantData();
}
}
}
Problem faced with the above code
No matter which way the getRelevantData function is getting called, im always getting the value of listOfDept as computer science. It is never going in the checkStart function of first controller.
In general I would discourage using static methods for this kind of initialization and instead inject the required data into constructors or create factory methods for creating object with necessary data.
But, if you do want to use static properties, the problem is that you need to refer to the right parent class in the getRelevantData implementation. The class that constructed the instance can be accessed through constructor property. TypeScript does not process this scenario well, so you have to make a type cast:
// firstController.ts
class firstController implements IFirstController {
// Need to be `protected` to be accessible from subclass
protected static listOfDept: string[];
static checkStart(){
firstController.listOfDept; // my logic to fill this object
}
constructor (){}
getRelevantData(next: (error: string, response: any) => void): void {
// You need to refer to the constructor
let Class = this.constructor as typeof firstController;
var myObject = Class.listOfDept;
// the rest
}
}
firstController.checkStart();
//ifirstController.ts
interface IFirstController {
getRelevantData(next: (error: string, response: any) => void): void;
}
// secondController.ts
class secondController extends firstController implements ISecondController {
// No `listOfDept` definition here
static checkStart(){
secondController.listOfDept = ["Computer Science"];
}
constructor (){
super();
}
}
secondController.checkStart();

Stackable functions

I am looking for a library agnostic way to "stack" functions. The paradigm's I am used to is "middleware", where something happens within a function errors can be thrown, and a context (or req) global is used to attach new properties or change existing ones. These ideas are found in libraries like express, or type-graphql.
I am looking for some agnostic way to chain middleware, not dependent on these type of libraries.
Here's an example of the kinds of functions I have.
I am struggling with some kind of clean way to author functions. The global approach is not complimentary to proper typing using typescript, and isn't very functional.
Where the more functional approach lacks this kind of "chainablity", where I can simply have an array of functions like below.
// logs the start of middleware
context.utility.log(debug, ids.onLoad),
// fetches user by email submitted
context.potentialUser.fetchByEmail(SignupOnSubmitArgs),
// throws error if the user is found
context.potentialUser.errorContextPropPresent,
// checks if passowrd and reenterPassword match
context.potentialUser.signupPassword(SignupOnSubmitArgs),
// creates the user
context.user.create(SignupOnSubmitArgs, ''),
// thows error if create failed in some way
context.user.errorContextPropAbsent,
// adds user id to session
context.utility.login,
// redirects user to dashboard
context.utility.redirect(Pages2.dashboardManage)
Is there any tools / libraries out there that will allow be to author clear and clean chain-able functions, and glue them together in a stackable way?
Returning this is usually the way for being able to chain methods. I made you an example showing both sync and async functions:
class ChainedOperations {
constructor(private value: number){}
public add(n: number): this {
this.value += n;
return this;
}
public subtract(n: number): this {
this.value -= n;
return this;
}
public async send(): Promise<this> {
console.log(`Sending ${this.value} somewhere`);
return this;
}
}
async function somewhereElse(): Promise<void> {
const firstChain = await new ChainedOperations(1).add(1).subtract(1).send();
await firstChain.add(1).subtract(2).send()
}
somewhereElse().catch(e => { throw new Error(e) });
For better dealing with async functions you can use pipe pattern where you chain but also wait for the final result and pass it to the next guy:
abstract class Pipable {
public pipe(...functions: Function[]) {
return (input: any) => functions.reduce((chain, func: any) => chain.then(func.bind(this)), Promise.resolve(input));
}
}
class AClass extends Pipable {
constructor(private value: number){
super();
}
public add(n: number): number {
this.value += n;
return this.value;
}
public subtract(n: number): number {
this.value -= n;
return this.value;
}
public async send(): Promise<number> {
console.log(`Sending ${this.value} somewhere`);
return this.value;
}
}
async function Something(){
const myClass = new AClass(2);
const composition = await myClass.pipe(myClass.add, myClass.subtract, myClass.send)(2);
}
Something();
Some people don't like to start from beginning but work their way backwards from the last function. If you want that just replace .reduce with .reduceRight. If you like fancy names, starting from last is called Composing as opposed to piping.

What is the Nest.js way of creating static and instance functions for a model?

Does such a thing exist or do I follow standard Mongoose procedure?
I read the docs, I spent the whole day yesterday for this, but I could only find relative ones that placed the functions inside the service component. This is not effective as if I would like to use a static model function outside of the service component (say, a custom decorator), it wouldn't reach it as DI is private.
I would have created an Issue on Github for documentation request, but I feared I may have overlooked something.
Edit 2: Please do not change the title of the post. "Nest" is not a typo for "best". It is referring to a Node.js framework called Nest.js. (See post tags and referenced documentation link)
Edit: In the MongoDB section of the docs, there's this piece of code:
constructor(#InjectModel(CatSchema) private readonly catModel: Model<Cat>) {}
but specifically, this Model<Cat> part, imports Cat from an interface that extends Mongoose Document interface. Wouldn't it be better if this Cat interface was a class instead which was capable of functions (even after transpilation)?
I use the following approach:
When defining the schema, add static methods to the Mongoose schema:
UserSchema.methods.comparePassword = async function(candidatePassword: string) {
return await bcrypt.compare(candidatePassword, this.password);
};
Also include the method in the object's interface definition:
export interface User {
firstName: string;
...
comparePassword(candidatePassword: string): Promise<boolean>;
}
as well as the UserDocument interface
export interface UserDocument extends User, Document { }
So now my UsersService:
export class UsersService {
constructor(#InjectModel(Schemas.User) private readonly userRepository: Model<UserDocument>,
private readonly walletService: WalletsService,
#Inject(Modules.Logger) private readonly logger: Logger) {}
async findByEmail(email: string): Promise<UserDocument> {
return await this.userRepository.findOne({ email }).select('password');
}
...
}
And to tie it all together, when a user tries to log in, the Auth service retrieves a user object by id, and invokes that user object's instance method of comparePassword:
#Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
) { }
async signIn({ email, password }: SignInDto): Promise<LoginResponse> {
const user = await this.usersService.findByEmail(email);
if (!user) { throw new UnauthorizedException('Invalid Username or Password'); }
if (await user.comparePassword(password)) {
const tokenPayload: JwtPayload = { userId: user.id };
const token = this.jwtService.sign(tokenPayload);
return ({ token, userId: user.id, status: LoginStatus.success });
} else {
throw new UnauthorizedException('Invalid Username or Password');
}
}
}
#InjectModel() is a helper decorator to inject registered component. You can always use a model class directly instead of injecting it through a constructor. Thanks to that you can use a model everywhere (but I'm not sure whether a custom decorator is a right choice). Also, Model<Cat> is redundant here. You can replace this type with anything else that fits your use-case, for example typeof X if you want to call static functions.

Resources