I work with github api on redux-toolkit(rtk-query) and have a problem - redux-toolkit

Uncaught Error: Warning: Middleware for RTK-Query API at reducerPath "github/api" has not been added to the store.
You must add the middleware for RTK-Query to function correctly!
import {configureStore} from "#reduxjs/toolkit";
import {githubApi} from "./github/github.api";
export const store = configureStore({
reducer: {
[githubApi.reducerPath]: githubApi.reducer
},
})
import {createApi, fetchBaseQuery} from "#reduxjs/toolkit/query/react";
export const githubApi = createApi({
reducerPath: 'github/api',
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.github.com/'
}),
endpoints: build => ({
searchUsers: build.query<any, string>({
query: (search: string) => ({
url: `search/users`,
params: {
q: search
}
})
})
})
})
export const {useSearchUsersQuery} = githubApi
import React from "react";
import {useSearchUsersQuery} from "../store/github/github.api";
export function HomePage() {
const {isLoading, isError, data} = useSearchUsersQuery('anyname')
return (
<div>Home</div>
)
}

It's literally what it says - in your configureStore call, you skipped adding the RTK Query middleware.
export const store = configureStore({
reducer: {
[githubApi.reducerPath]: githubApi.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(githubApi.middleware),
})

Related

Issue testing RTK Query: The preloadedState argument passed to createStore has unexpected type of "array"

I'm learning RTK Query.
So, in a test, I have the following error:
The preloadedState argument passed to createStore has unexpected type of "array". Expected argument to be an object with the following keys: "queries", "mutations", "provided", "subscriptions", "config"
This is my test:
test("Can use preloadedState", () => {
const initialPosts = [
{
id: 1,
body: "Lorem ipsum",
},
];
// wrap component with custom render function
renderWithProviders(<GenericList />, {
preloadedState: {
postsSlice: initialPosts,
},
});
const loremIpsum = screen.getByText(/lorem ipsum/i);
expect(loremIpsum).toBeInTheDocument();
});
I have followed this tutorial https://redux.js.org/usage/writing-tests#preparing-initial-test-state.
This is my test-utils.js file:
import React from 'react'
import { render } from '#testing-library/react'
import { Provider } from 'react-redux'
import { setupStore } from '../../store/index'
import { setupListeners } from '#reduxjs/toolkit/dist/query'
export function renderWithProviders(
ui,
{
preloadedState = {},
// Automatically create a store instance if no store was passed in
store = setupStore(preloadedState),
...renderOptions
} = {}
) {
setupListeners(store.dispatch);
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }
}
This is my store:
import { configureStore } from "#reduxjs/toolkit";
import { postsSlice } from "../features/postsSlice";
export const setupStore = preloadedState => {
return configureStore({
reducer: {
[postsSlice.reducerPath]: postsSlice.reducer,
},
preloadedState,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
immutableCheck: false,
serializableCheck: false,
}).concat(postsSlice.middleware),
})
}
And finally this is the postsSlice
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
export const postsSlice = createApi({
// Reducer Path it's name shown on Redux Tab
reducerPath: "postsSlice",
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_BACKEND_URL,
}),
// With tag type we can invalidate cache
tagTypes: ['posts'],
endpoints: (builder) => ({
getPosts: builder.query({
query: () => "/posts"
})
})
});
export const { useGetPostsQuery } = postsSlice;
You cannot just make up some random contents for your initialState, it has to be exactly the structure of your Redux state. And for RTK Query, that is a very complex internal structure that you should probably not mock (it could change in another version!).
Honestly, to your last question, this is a step backwards - if you want to test RTK Query, test it with a normal Redux store and mock the api.
All you were missing was to wait in your test until the result was rendered.
Faking internal data structures means that your test will just test a very small part of what actually happens.

Mongo Memory Server: Property 'getUri' does not exist on type '(opts?: MongoMemoryServerOpts) => Promise<MongoMemoryServer>'

I am trying to run e2e tests on a nestjs app.
I have trouble running MongoMemoryServer, in order to run set the MMS I used this article based on the original nestJs documentation.
I keep getting this error:
test/user-preferences.e2e-spec.ts:27:32 - error TS2339: Property 'getUri' does not exist on type '(opts?: MongoMemoryServerOpts) => Promise<MongoMemoryServer>'.
27 const uri = mongod.getUri();
~~~~~~
Test Suites: 1 failed, 1 total
This is the test I try to run:
import { Test, TestingModule } from '#nestjs/testing';
import { getModelToken, MongooseModule } from '#nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import {
UserPreferences,
UserPreferencesDocument,
UserPreferencesSchema,
} from './../src/user-preferences/schemas/user-preferences.schema';
import { UserPreferencesModule } from './../src/user-preferences/user-preferences.module';
import * as request from 'supertest';
import { factory } from 'fakingoose';
import { Model } from 'mongoose';
describe('userPreferences controller', () => {
let userPreferencesModel;
let app;
const UserPreferencesFactory = factory<UserPreferencesDocument>(
UserPreferencesSchema,
).setGlobalObjectIdOptions({ tostring: false });
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
await MongooseModule.forRootAsync({
useFactory: async () => {
const mongod = await MongoMemoryServer.create;
const uri = mongod.getUri();
return {
uri: uri,
};
},
}),
UserPreferencesModule,
],
}).compile();
app = moduleFixture.createNestApplication();
console.log('app: ', app);
userPreferencesModel = moduleFixture.get<Model<UserPreferencesDocument>>(
getModelToken(UserPreferences.name),
);
await app.init();
});
beforeEach(() => {
// populate the DB with 1 UserPreference using fakingoose
const mockUserPreferences = UserPreferencesFactory.generate();
return userPreferencesModel.create(mockUserPreferences);
});
afterEach(() => userPreferencesModel.remove({}));
it('GET /api/v1/user-preferences', () => {
return request(app.getHttpServer())
.get('/api/v1/user-preferences')
.expect(200)
.expect((res) => {
console.log('res: ', res);
expect(res.body.length > 0).toBe(true);
});
});
afterAll(() => {
app.close();
});
});
This is my schema file:
import { Schema, Prop, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export enum exitToOptions {
THIS_POST = 'this_post',
ALL_POSTS = 'all_posts',
DASHBOARD = 'dashboard',
}
export type UserPreferencesDocument = UserPreferences & Document;
#Schema()
export class UserPreferences {
#Prop({ unique: true })
eUserId: string;
#Prop()
uiTheme: string;
#Prop()
panelWidth: number;
#Prop()
editingHandles: boolean;
#Prop()
enableLightboxInEditor: boolean;
#Prop()
hiddenElements: boolean;
#Prop()
defaultDeviceView: string;
// #Prop()
// exitTo: exitToOptions
#Prop()
exitTo: string;
}
export const UserPreferencesSchema =
SchemaFactory.createForClass(UserPreferences);

reference error when I try to get a value from other slice redux toolkit?

I am trying to import a value form other slice that has some user information, any idea why I am getting this nasty error ? I read it is normal to request data from other slices, the error seem to be like the slice cannot find the store... below is my code structure, my store is at the top of my app, does this getState function works in a component only and not in slice to other slice .
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter } from 'react-router-dom';
import App from './App';
import './index.css';
// Redux Tool Kit
import { store } from './app/store';
import { Provider } from 'react-redux';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
import {
RootState,
store
} from './store';
import {
createSlice,
PayloadAction
} from '#reduxjs/toolkit';
export interface miscState {
dayNumber: true,
dayOfWeek: false,
};
export const miscSlice = createSlice({
name: 'misc',
initialState,
reducers: {
setDisplayDay: (state, action: PayloadAction < {
bool: boolean;type: string
} > ) => {
const {
user,
uid
} = store.getState().global.currentUser;
const setDisplay = async() => {
const docRef = doc(db, colDynamic(user)[0], uid);
await updateDoc(docRef, {
[action.payload.type]: action.payload.bool,
});
};
},
},
});
// Values
export const MiscCurrentState = (state: RootState) => state.misc;
// Action creators are generated for each case reducer function
export const {
setDisplayDay
} = miscSlice.actions;
export default miscSlice.reducer;
import { configureStore } from '#reduxjs/toolkit';
// Global
import globalReducer from './globalSlice';
// Misc
import miscReducer from './miscSlice';
export const store = configureStore({
reducer: {
global: globalReducer,
misc: miscReducer,
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
In Redux, you are not allowed to access the store from within a reducer. A reducer has to be a pure function that only reads the variables passed into it - so all information you have is the slice's own state and the action being dispatched. You are not allowed to read a global variable, have any kind of side effect or read from the global Redux store to get the data of another slice.

Problem calling mongoose service with nestjs in jest testing

Currently i'm trying to test my service but always fails and prints the error, even when the app is running correctly and the service is working
Error
TypeError: undefined is not a function
at Array.find (<anonymous>)
at NewsService.findAll (MY_ROUTE\src\news\news.service.ts:28:8)
at Object.it (MY_ROUTE\src\news\news.service.spec.ts:33:10)
at Object.asyncJestTest (MY_ROUTE\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:106:37)
at resolve (MY_ROUTE\node_modules\jest-jasmine2\build\queueRunner.js:45:12)
at new Promise (<anonymous>)
at mapper (MY_ROUTE\node_modules\jest-jasmine2\build\queueRunner.js:28:19)
at promise.then (MY_ROUTE\node_modules\jest-jasmine2\build\queueRunner.js:75:41)
at process._tickCallback (internal/process/next_tick.js:68:7)
at service.findAll.then.catch (news/news.service.spec.ts:39:19)
news.service.spec.ts
import { NewsService } from './news.service';
import { Model } from 'mongoose';
import { News, NewsSchema } from './schemas/news.schema';
import { getModelToken } from '#nestjs/mongoose';
describe('NewsService', () => {
let service: NewsService;
const mockRepository = (...args: any[]) => { };
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [NewsService, { provide: getModelToken(News.name), useFactory: mockRepository }],
}).compile();
service = module.get<NewsService>(NewsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('get news', () => {
it('should get all news', async () => {
service
.findAll()
.then((allNews) => {
console.log(allNews);
expect(allNews).toBeDefined();
})
.catch((error) => {
console.log(error);
});
});
});
});
news.schema.ts
import { Document } from 'mongoose';
export type NewsDocument = News & Document;
#Schema()
export class News extends Document {
#Prop({ unique: true })
id: string;
#Prop()
title: string;
#Prop()
date: string;
}
export const NewsSchema = SchemaFactory.createForClass(News);
news.service.ts
import { Model } from 'mongoose';
import { InjectModel } from '#nestjs/mongoose';
import { News } from './schemas/news.schema';
#Injectable()
export class NewsService {
constructor(
#InjectModel(News.name) private readonly newsModel: Model<News>
) {}
public async findAll(): Promise<News[]> {
return await this.newsModel
.find()
.sort([['date', 'descending']])
.exec();
}
}
I'm just learning about Jest, but after a lot of research and tests, i couldn't figured out what i'm doing wrong exactly.
EDIT
This is the only "decent" thing that i've tried to, but other errors appear. Maybe my whole focus on this is wrong.
const mockRepository = (...args: any[]) => {
findAll: jest.fn().mockReturnValue([
new News({
id: '1',
title: 'title',
date: 'date',
}),
]);
};
Error
TypeError: Cannot read property 'plugin' of undefined
5 |
6 | #Schema()
> 7 | export class News extends Document {
| ^
8 | #Prop({ unique: true })
9 | id: string;
10 |
at News.Object.<anonymous>.Document.$__setSchema (../node_modules/mongoose/lib/document.js:3028:10)
at new Document (../node_modules/mongoose/lib/document.js:86:10)
at new News (news/schemas/news.schema.ts:7:1)
at InstanceWrapper.mockRepository [as metatype] (news/news.service.spec.ts:13:7)
at Injector.instantiateClass (../node_modules/#nestjs/core/injector/injector.js:293:55)
at callback (../node_modules/#nestjs/core/injector/injector.js:77:41)

How do I pass dynamic param in nestjs facebook strategy callback url

How do i pass some dynamic params in the facebook login callback url?
I have different types of users (differentiated by a 'type' param) signing up using facebook login. I have created a facebook auth strategy using passport-facebook which works fine.
However after authentication, when callback url is called, i need to know which type of user requested the signup.
I'm guessing i can pass a param when defining the callback url
something like this
http://localhost:3000/auth/facebook/callback/type1
http://localhost:3000/auth/facebook/callback/type2
How do I pass a dynamic value into the FacebookStrategy??
or whats the possible workaround to achieve this?
// PassportStrategy.ts
#Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
clientID: 'MYID',
clientSecret: 'MYSCRET',
callbackURL: "http://localhost:3000/auth/facebook/callback",
profileFields: ['id', 'displayName', 'emails', 'photos']
});
}
async validate(accessToken: any, refreshToken: any, profile: any) {
return {
name: profile.displayName,
email: profile.emails[0].value,
provider: "facebook",
providerId: profile.id,
photo: profile.photos[0].value
}
}
}
// auth controller
#Controller('auth')
export class AuthController {
constructor(
#Inject(forwardRef(() => AuthService)) private readonly authService: AuthService,
) { }
#Get('/facebook')
#UseGuards(AuthGuard('facebook'))
async facebookAuth(#Request() req) {
return
}
#UseGuards(AuthGuard('facebook'))
#Get('/facebook/callback')
async facebookCallback(#Request() req) {
return this.authService.login(req.user);
}
}
Basically i want to be able to call "/auth/facebook/:type" and pass the type value in the callback url defined in the Strategy
and callback endpoint to be something like "/auth/facebook/callback/:type"
so when i call the authservice.login function i can pass that 'type' and decide which type of user to be created if its the first time signup
Guide me if my approach is wrong. Thanks
I have been dealing recently with a similar issue here is my approach. Probably is not the best but works for now.
import { Inject, Injectable, Logger } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import passport = require('passport');
import { Strategy } from 'passport-facebook';
#Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy, 'facebook') {
private readonly logger = new Logger(FacebookStrategy.name);
constructor(
#Inject('FACEBOOK_STRATEGY_CONFIG')
private readonly facebookStrategyConfig,
) {
super(
facebookStrategyConfig,
async (
request: any,
accessToken: string,
refreshToken: string,
profile: any,
done,
) => {
this.logger.log(profile);
// take the state from the request query params
const { state } = request.query;
this.logger.log(state);
// register user
// return callback
return done(null, profile);
},
);
passport.use(this);
}
}
import { Controller, Get, HttpStatus, Inject, Param, Query, Req } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { Redirect } from '#nestjsplus/redirect';
#Controller('auth')
export class AuthController {
#Inject('ConfigService')
private readonly configService: ConfigService;
#Get(':provider/callback')
#Redirect()
async socialCallback(#Req() req, #Param('provider') provider: string, #Query('state') state: string) {
// here you can use the provider and the state
return {
statusCode: HttpStatus.FOUND,
url: `${this.configService.get('FRONTEND_HOST')}/dashboard`,
};
}
}
import { MiddlewareConsumer, Module, NestModule } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { AuthController } from './auth.controller';
import { FacebookStrategy } from './facebook.strategy';
import passport = require('passport');
const facebookStrategyConfigFactory = {
provide: 'FACEBOOK_STRATEGY_CONFIG',
useFactory: (configService: ConfigService) => {
return {
clientID: `${configService.get('FACEBOOK_CLIENT_ID')}`,
clientSecret: `${configService.get('FACEBOOK_CLIENT_SECRET')}`,
callbackURL: `${configService.get('FACEBOOK_OAUTH_REDIRECT_URI')}/callback`,
profileFields: ['id', 'displayName', 'link', 'photos', 'emails', 'name'],
passReqToCallback: true,
};
},
inject: [ConfigService],
};
#Module({
controllers: [AuthController],
providers: [facebookStrategyConfigFactory, FacebookStrategy],
})
export class AuthModule implements NestModule {
public configure(consumer: MiddlewareConsumer) {
const facebookLoginOptions = {
session: false,
scope: ['email'],
state: null,
};
consumer
.apply((req: any, res: any, next: () => void) => {
const {
query: { state },
} = req;
facebookLoginOptions.state = state;
next();
}, passport.authenticate('facebook', facebookLoginOptions))
.forRoutes('auth/facebook/*');
}
}
Now let me explain a little bit :D. The trick is in the middleware configuration.
const facebookLoginOptions = {
session: false,
scope: ['email'],
state: null,
};
consumer
.apply((req: any, res: any, next: () => void) => {
const {
query: { state },
} = req;
facebookLoginOptions.state = state;
next();
}, passport.authenticate('facebook', facebookLoginOptions))
.forRoutes('auth/facebook/*');
So, oAuth has this feature that you can pass a state param through the login flow.
By extracting the passport option in a variable we can change the state param dynamically by applying another middleware before the passport one.
In this way, you can call now http://localhost:3000/auth/facebook/login?state=anything-you-want
and this state query param will be passed through the strategy and also in the callback call.
I have also created a git repo with the example: https://github.com/lupu60/passport-dynamic-state
Another approach: the need was to dynamically set server url. It gets it using Context/Request.
// Custom Guard:
export const DynamicAuthGuard = (type?: string): Type<IAuthGuard> => {
const endpoint = `auth/${type}/redirect`
return class extends AuthGuard(type) {
getAuthenticateOptions(context: ExecutionContext) {
const httpContext: HttpArgumentsHost = context.switchToHttp()
const req: Request = httpContext.getRequest<Request>()
const serverURL = `${req.protocol}://${req.get('host')}`
const args = 'foo=bar'
const callbackURL = `${serverURL}/${endpoint}?${args}`
return {callbackURL}
}
}
}
// In controller 'auth':
#UseGuards(DynamicAuthGuard('facebook')) // or any passport strategy
#Get('facebook/redirect')
async facebookRedirect(#Req() req: Request, #Res() res: Response) {
// ...
}