How to upload files when unit testing firebase storage security rules? - node.js

I have a bucket into which users can upload audio files, and my goal is to test that it is working as expected. But when I try to use the "#firebase/rules-unit-testing" library to upload a test file into the emulator the upload gets stuck and makes no progress.
Rules
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if false;
}
match /audio/{audioFileID} {
allow create: if request.auth != null && request.resource.contentType.matches('audio/(flac|wav)');
}
}
}
Test code
import {
assertFails,
assertSucceeds,
initializeTestEnvironment,
RulesTestEnvironment,
} from "#firebase/rules-unit-testing";
import { test, beforeAll, beforeEach, afterAll } from "vitest";
import fs from "fs";
const createTestFile = (size: number) => Buffer.alloc(size);
let testEnv: RulesTestEnvironment;
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: "stst-et-interviewer-dev",
hub: {
host: "localhost",
port: 4400,
},
});
});
beforeEach(async () => {
await testEnv.clearStorage();
await testEnv.clearFirestore();
});
afterAll(async () => await testEnv.cleanup());
const loadStepStoneImage = () =>
fs.readFileSync("./public/images/stepstoneLogo.svg");
test("Storage does not allow you to read files", async () => {
const rouge = testEnv.unauthenticatedContext();
const alice = testEnv.authenticatedContext("alice");
await assertFails(
rouge.storage().ref("audio/test-interview.flac").getDownloadURL()
);
await assertFails(
alice.storage().ref("audio/test-interview.flac").getDownloadURL()
);
await assertFails(alice.storage().ref("audio/").listAll());
});
test("Storage does not allow you to upload a file if you are not logged in", async () => {
const rouge = testEnv.unauthenticatedContext();
const upload = rouge
.storage("stst-et-interviewer-dev.appspot.com")
.ref("audio/logo.svg")
.put(createTestFile(200), { contentType: "audio/flac" });
await assertFails(upload.then()); // <- GETS STUCK HERE
console.log("this does not happen");
});
What are the possible reasons for this happening? Are there any simple ways of debugging?

Related

Jest mocks only work at top of file, never inside "describe" or "it" blocks

I'm trying to introduce a small change to an existing project without unit tests and decided I'd try to learn enough about nodejs and jest to include tests with my change. However, I cannot get mocks to work like I'd expect in, say, python. The project uses the "kubernetes-client" library from godaddy and tries to create a config object from the envvar "KUBECONFIG", like this:
// a few lines into server.js
// Instantiate Kubernetes client
const Client = require('kubernetes-client').Client
const config = require('kubernetes-client').config;
if (process.env.KUBECONFIG) {
client = new Client({
config: config.fromKubeconfig(config.loadKubeconfig(process.env.KUBECONFIG)),
version: '1.13'
});
}
else {
client = new Client({ config: config.getInCluster(), version: '1.9' });
}
In my testing environment, I don't want any API calls, so I'm trying to mock it out:
// __tests__/server.test.js
// Set up mocks at the top because of how server.js sets up k8s client
const k8sClient = require('kubernetes-client');
const narp = 'narp';
jest.mock('kubernetes-client', () => {
const noConfigmaps = jest.fn(() => {
throw narp;
});
const namespaces = jest.fn().mockReturnValue({
configmaps: jest.fn().mockReturnValue({
get: noConfigmaps
})
});
const addCustomResourceDefinition = jest.fn().mockReturnThis()
const mockClient = {
api: {
v1: {
namespaces
}
},
addCustomResourceDefinition: jest.fn().mockReturnThis(),
};
return {
Client: jest.fn(() => mockClient),
config: {
fromKubeconfig: jest.fn().mockReturnThis(),
loadKubeconfig: jest.fn().mockReturnThis(),
getInCluster: jest.fn().mockReturnThis()
},
};
});
const app = require('../server.js')
const supertest = require('supertest');
const requestWithSuperTest = supertest(app.app);
describe('Testing server.js', () => {
afterAll(() => {
app.server.close();
});
describe('Tests with k8s client throwing error when fetching configmaps', () => {
it("finds a resource's ingressGroup by name", () => {
var resource = {
"spec": {
"ingressClass": "foo",
"ingressTargetDNSName": "foo"
}
};
var ingressGroups = [
{
"ingressClass": "bar",
"hostName": "bar",
"name": "barName"
},
{
"ingressClass": "foo",
"hostName": "foo",
"name": "fooName"
}
];
expect(app.findMatchingIngressGroupForResource(resource, ingressGroups)).toBe("fooName");
});
it('GET /healthcheck should respond "Healthy"', async () => {
const resp = await requestWithSuperTest.get('/healthcheck');
console.log("Response in Testing Endpoints: " + JSON.stringify(resp));
expect(resp.status).toEqual(200);
expect(resp.type).toEqual(expect.stringContaining('text'));
expect(resp.text).toEqual('Healthy');
});
it('Tests getIngressGroups() rejects with error when it cannot get configmaps', async () => {
app.getIngressGroups()
.then()
.catch(error => {
expect(error).toEqual("Failed to fetch Ingress Groups: " + narp);
});
});
});
});
With this setup, the tests pass (although I suspect it's meaningless). If I try to move the mocks inside the describe or it block using a beforeEach function (or not) so that I can change the behavior to return mock data instead of throwing an error, I immediately get errors with the k8s client complaining it can't find my kubeconfig/clusterconfig:
$ npm run testj
> testj
> jest --detectOpenHandles
kubernetes-client deprecated require('kubernetes-client').config, use require('kubernetes-client/backends/request').config. server.js:45:44
kubernetes-client deprecated loadKubeconfig see https://github.com/godaddy/kubernetes-client/blob/master/merging-with-kubernetes.md#request-kubeconfig- server.js:49:42
FAIL __tests__/server.test.js
● Test suite failed to run
ENOENT: no such file or directory, open 'NOT_A_FILE'
44 | if (process.env.KUBECONFIG) {
45 | client = new Client({
> 46 | config: config.fromKubeconfig(config.loadKubeconfig(process.env.KUBECONFIG)),
| ^
47 | version: '1.13'
48 | });
49 | }
at node_modules/kubernetes-client/backends/request/config.js:335:37
at Array.map (<anonymous>)
at Object.loadKubeconfig (node_modules/kubernetes-client/backends/request/config.js:334:28)
at Object.eval [as loadKubeconfig] (eval at wrapfunction (node_modules/kubernetes-client/node_modules/depd/index.js:425:22), <anonymous>:5:11)
at Object.<anonymous> (server.js:46:46)
If anybody has run into this kind of behavior before or sees some obviously-wrong lines, I'd really appreciate any tips or information. Thanks!
I had to change a few things to get this working:
jest.doMock() instead of jest.mock()
use of let app inside the describe block instead of const app at module-scope
a beforeEach() which calls jest.resetModules()
an afterEach() which calls app.close()
in the it block which overrides the mock(s), explicitly call jest.resetModules() before overriding
in the it block which overrides the mock(s), call app.close() and re-initialize app before invoking the actual function-under-test/expect
Resulting test file:
// Set up mocks at the top because of how server.js sets up k8s client
const k8sClient = require('kubernetes-client');
const supertest = require('supertest');
const narp = 'narp';
describe('Testing server.js', () => {
let app;
let requestWithSuperTest;
beforeEach(() => {
jest.resetModules();
jest.doMock('kubernetes-client', () => {
const noConfigmaps = jest.fn(() => {
throw narp;
});
const namespaces = jest.fn().mockReturnValue({
configmaps: jest.fn().mockReturnValue({
get: noConfigmaps
})
});
const addCustomResourceDefinition = jest.fn().mockReturnThis()
const mockClient = {
api: {
v1: {
namespaces
}
},
addCustomResourceDefinition: jest.fn().mockReturnThis(),
};
return {
Client: jest.fn(() => mockClient),
config: {
fromKubeconfig: jest.fn().mockReturnThis(),
loadKubeconfig: jest.fn().mockReturnThis(),
getInCluster: jest.fn().mockReturnThis()
},
};
});
app = require('../server.js');
requestWithSuperTest = supertest(app.app);
});
afterEach(() => {
app.server.close();
});
it("finds a Resource's ingressGroup by name", () => {
var resource = {
"spec": {
"ingressClass": "foo",
"ingressTargetDNSName": "foo"
}
};
var ingressGroups = [
{
"ingressClass": "bar",
"hostName": "bar",
"name": "barName"
},
{
"ingressClass": "foo",
"hostName": "foo",
"name": "fooName"
}
];
expect(app.findMatchingIngressGroupForResource(resource, ingressGroups)).toBe("fooName");
});
it('GET /healthcheck should respond "Healthy"', async () => {
const resp = await requestWithSuperTest.get('/healthcheck');
console.log("Response in Testing Endpoints: " + JSON.stringify(resp));
expect(resp.status).toEqual(200);
expect(resp.type).toEqual(expect.stringContaining('text'));
expect(resp.text).toEqual('Healthy');
});
it('Tests getIngressGroups() rejects with error when it cannot get configmaps', async () => {
expect.assertions(1);
await app.getIngressGroups()
.catch(error => {
expect(error).toEqual("Failed to fetch Ingress Groups: " + narp);
});
});
it('Tests getIngressGroups() succeeds when it gets configmaps', async () => {
expect.assertions(1);
jest.resetModules();
jest.doMock('kubernetes-client', () => {
const noConfigmaps = jest.fn(() => {
console.log('Attempted to get mocked configmaps');
return Promise.resolve({
body: {
items: []
}
});
});
const namespaces = jest.fn().mockReturnValue({
configmaps: jest.fn().mockReturnValue({
get: noConfigmaps
})
});
const addCustomResourceDefinition = jest.fn().mockReturnThis()
const mockClient = {
api: {
v1: {
namespaces
}
},
addCustomResourceDefinition: jest.fn().mockReturnThis(),
};
return {
Client: jest.fn(() => mockClient),
config: {
fromKubeconfig: jest.fn().mockReturnThis(),
loadKubeconfig: jest.fn().mockReturnThis(),
getInCluster: jest.fn().mockReturnThis()
},
};
});
app.server.close();
app = require('../server.js');
await app.getIngressGroups()
.then(result => {
expect(result).toEqual([])
});
});
});

Rxdb sync not update db

There are 3 bases (front, node, remote). Front <=> node, node <=> remote. When the front base is updated, the data goes to the remote base, but the node is not updated. In theory, the node should be updated first, and then the remote base.
Render db
addPouchPlugin(PouchdbAdapterIdb)
addPouchPlugin(PouchHttpPlugin)
addRxPlugin(RxDBReplicationCouchDBPlugin)
addRxPlugin(RxDBMigrationPlugin)
addRxPlugin(RxDBLeaderElectionPlugin)
addRxPlugin(RxDBQueryBuilderPlugin)
addRxPlugin(RxDBAjvValidatePlugin)
addRxPlugin(RxDBUpdatePlugin)
export const createDb = async () => {
console.log('[src/renderer/database/createDb] createDb')
const productsName = collectionName.getCollectionProductsName()
const documentsName = collectionName.getCollectionDocumentsName()
const settingsName = collectionName.getCollectionSettingsName()
const db = await createRxDatabase<Collections>({
name: 'renderer',
// use pouchdb with the indexeddb-adapter as storage engine.
storage: getRxStoragePouch('idb'),
})
await initCommonCollections({ db, documentsName, productsName, settingsName })
syncDbCollections(db, [productsName, documentsName, settingsName])
db.$.subscribe(({ operation, documentId, documentData }) => {
if (documentData.type === SettingsTypes.DEVICE_SETTING) {
console.log(`Change database RENDER event:\n ${operation}, \n documentData:`, documentData)
}
})
return db
}
Render sync
const remoteDbUrl = `http://localhost:3030/db/`
const logPath = '[src/renderer/database/syncDbCollections]'
export const syncDbCollections = (db: RxDatabase<Collections>, collectionNames: (keyof Collections)[]) => {
console.log('syncDbCollections', collectionNames)
collectionNames.forEach(name => {
const rxReplicationState = db.collections[name].syncCouchDB({
remote: `${remoteDbUrl}${name}`,
options: {
live: true,
retry: true,
},
})
rxReplicationState.error$.subscribe(error => {
console.error(logPath, name, 'error', JSON.stringify(error))
})
})
}
Node base
addPouchPlugin(PouchdbAdapterHttp)
addPouchPlugin(LevelDbAdapter)
addRxPlugin(RxDBAjvValidatePlugin)
addRxPlugin(RxDBMigrationPlugin)
addRxPlugin(RxDBServerPlugin)
addRxPlugin(RxDBLeaderElectionPlugin)
addRxPlugin(RxDBQueryBuilderPlugin)
addRxPlugin(RxDBUpdatePlugin)
addRxPlugin(RxDBReplicationCouchDBPlugin)
let db: RxDatabase<Collections>
export const getMainDb = () => {
if (!db) {
throw new Error('No available database.')
}
return db
}
export const getDocumentCollection = (): DocumentsRxCol => {
return db[collectionNames.getCollectionDocumentsName()]
}
export const getSettingsCollection = (): SettingsRxCol => {
return db[collectionNames.getCollectionSettingsName()]
}
export const getProductsCollection = (): ProductsRxCol => {
return db[collectionNames.getCollectionProductsName()]
}
export const initDatabase = async () => {
console.log(logPathAlias, 'initDatabase')
if (db) {
console.warn(logPathAlias, 'db instance already created!')
return db
}
db = await createRxDatabase<Collections>({
name: `${electronApp.getPath('userData')}/db`,
storage: getRxStoragePouch(LevelDown),
})
const productsName = collectionNames.getCollectionProductsName()
const documentsName = collectionNames.getCollectionDocumentsName()
const settingsName = collectionNames.getCollectionSettingsName()
await initCommonCollections({ db, productsName, documentsName, settingsName })
await syncCollections([productsName, documentsName, settingsName])
db.$.subscribe(({ operation, documentId, documentData }) => {
// if (documentData.type === SettingsTypes.DEVICE_SETTING) {
console.log(`Change database NODE event:\n ${operation}, \n documentData:`, documentData)
// }
})
const { app } = await db.server({
startServer: false, // (optional), start express server
// options of the pouchdb express server
cors: false,
pouchdbExpressOptions: {
inMemoryConfig: true, // do not write a config.json
logPath: `${electronApp.getPath('temp')}/rxdb-server.log`, // save logs in tmp folder
},
})
return app
}
const lastRetryTime = {}
const syncCollections = async (collections: CollectionNames[]) => {
collections.map(collectionName => {
const rxReplicationState = db.collections[collectionName].syncCouchDB({
remote: `${CouchDbServerUrl}/${collectionName}`,
options: {
live: true,
retry: true,
// #ts-ignore
// headers: {
// Authorization: `Bearer ${getAccessToken()}`,
// },
},
})
rxReplicationState.error$.subscribe(async error => {
console.error(logPathAlias, collectionName, String(error))
if (error.status === 401 && dayjs().diff(lastRetryTime[collectionName], 'seconds') > 10 && getIsRefreshFresh()) {
lastRetryTime[collectionName] = dayjs()
await rxReplicationState.cancel()
await refreshTokens()
await syncCollections([collectionName])
}
})
})
}
No errors
Moreover, if you save data in a remote database, then they are synchronized with the node
Help me :(

Jest mock Knex transaction

I have the following lambda handler to unit test. It uses a library #org/aws-connection which has a function mysql.getIamConnection which simply returns a knex connection.
Edit: I have added the mysql.getIamConnection function to the bottom of the post
Edit: If possible, I'd like to do the testing with only Jest. That is unless it becomes to complicated
index.js
const {mysql} = require('#org/aws-connection');
exports.handler = async (event) => {
const connection = await mysql.getIamConnection()
let response = {
statusCode: 200,
body: {
message: 'Successful'
}
}
try {
for(const currentMessage of event.Records){
let records = JSON.parse(currentMessage.body);
await connection.transaction(async (trx) => {
await trx
.table('my_table')
.insert(records)
.then(() =>
console.log(`Records inserted into table ${table}`))
.catch((err) => {
console.log(err)
throw err
})
})
}
} catch (e) {
console.error('There was an error while processing', { errorMessage: e})
response = {
statusCode: 400,
body: e
}
} finally {
connection.destroy()
}
return response
}
I have written some unit tests and I'm able to mock the connection.transaction function but I'm having trouble with the trx.select.insert.then.catch functions. H
Here is my testing file
index.test.js
import { handler } from '../src';
const mocks = require('./mocks');
jest.mock('#org/aws-connection', () => ({
mysql: {
getIamConnection: jest.fn(() => ({
transaction: jest.fn(() => ({
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis()
})),
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis(),
destroy: jest.fn().mockReturnThis()
}))
}
}))
describe('handler', () => {
test('test handler', async () =>{
const response = await handler(mocks.eventSqs)
expect(response.statusCode).toEqual(200)
});
});
This test works partially but it does not cover the trx portion at all. These lines are uncovered
await trx
.table('my_table')
.insert(records)
.then(() =>
console.log(`Records inserted into table ${table}`))
.catch((err) => {
console.log(err)
throw err
})
How can set up my mock #org/aws-connection so that it covers the trx functions as well?
Edit:
mysql.getIamConnection
async function getIamConnection (secretId, dbname) {
const secret = await getSecret(secretId)
const token = await getToken(secret)
let knex
console.log(`Initialzing a connection to ${secret.proxyendpoint}:${secret.port}/${dbname} as ${secret.username}`)
knex = require('knex')(
{
client: 'mysql2',
connection: {
host: secret.proxyendpoint,
user: secret.username,
database: dbname,
port: secret.port,
ssl: 'Amazon RDS',
authPlugins: {
mysql_clear_password: () => () => Buffer.from(token + '\0')
},
connectionLimit: 1
}
}
)
return knex
}
Solution
#qaismakani's answer worked for me. I wrote it slightly differently but the callback was the key. For anyone interested here is my end solution
const mockTrx = {
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockResolvedValue()
}
jest.mock('#org/aws-connection', () => ({
mysql: {
getIamConnection: jest.fn(() => ({
transaction: jest.fn((callback) => callback(mockTrx)),
destroy: jest.fn().mockReturnThis()
}))
}
}))
Updating your mock to look like this might do the trick:
const { mysql } = require("#org/aws-connection");
jest.mock("#org/aws-connection", () => ({
mySql: {
getIamConnection: jest.fn()
}
}));
const mockTrx = {
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockResolveValue() // Resolve any data here
};
mysql.getIamConnection.mockReturnValue({
transaction: jest.fn((callback) => callback(mockTrx)),
});
You need to mock the transaction so that it executes your callback with a dummy trx. To do this, you need to make sure that all the functions inside the trx object return a reference back to it or a promise so that you can chain it appropriately.
Instead of mocking knex implementation, I've written knex-mock-client which allows you to mimic real db with an easy API.
Change your mock implementation with
import { handler } from "../src";
import { getTracker } from "knex-mock-client";
const mocks = require("./mocks");
jest.mock("#org/aws-connection", () => {
const knex = require("knex");
const { MockClient } = require("knex-mock-client");
return {
mysql: {
getIamConnection: () => knex({ client: MockClient }),
},
};
});
describe("handler", () => {
test("test handler", async () => {
const tracker = getTracker();
tracker.on.insert("my_table").responseOnce([23]); // setup's a mock response when inserting into my_table
const response = await handler(mocks.eventSqs);
expect(response.statusCode).toEqual(200);
});
});

Test ApolloClient API with Jest in Vue3 (Composition API)

I am using Vue3 (typescript) with Composition API for my application. I am using ApolloClient grapghql for API calls. I have created a separate service file for API calls. (PFB files)
Service file
import { ApolloClient, InMemoryCache, HttpLink } from "#apollo/client/core"
import { gql } from "#apollo/client/core"
import fetch from 'cross-fetch';
const httpLink = new HttpLink({
uri: process.env.VUE_APP_BACKEND_GRAPHQL_URI,
fetch
})
const apolloClient = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
})
export const getAplloAPIdata = async (reqQuery: any) => {
const query = gql `${reqQuery}`
try {
return await apolloClient.query({ query })
}catch {
console.log('API error')
}
}
Home.vue
setup() {
const threatList = ref([])
const threat = ref(null)
// get all threats
const getThreats = async () => {
const getThreatsQuery = `
query {
threats {
short_description
threat_level
}
}
`
try {
const result = await getAplloAPIdata(getThreatsQuery)
if (result) {
threatList.value = result.data.threats
}
} catch {
console.log('Error receiving threats data')
}
}
Can you please tell me how can I write test cases to mock this API in jest? Thank you!
I would mock getAplloAPIdata to return mock data, and verify that data in your test. The key is to make sure the mock path is the same as that imported in your component:
// Home.vue
import { getAplloAPIdata } from '#/service'
/*...*/
// Home.spec.js
jest.mock('#/service', () => {
return {
getAplloAPIdata: () => ({
data: {
threats: [{ id: 123456 }]
}
})
}
})
describe('Home.vue', () => {
it('gets threats', async () => {
const wrapper = shallowMount(Home)
await wrapper.vm.getThreats()
expect(wrapper.vm.threatList).toContainEqual({ id: 123456 })
})
})
GitHub demo

Unit test for customPollingHook which uses apollo useLazyQuery

So I have written a custom polling hook which uses useContext and useLazyQuery hooks. I want to write a unit test for this, which should cover its returned values state and side effect.
So far I have managed to do this much but I'm not so sure how to proceed ahead. Any tips?
export const useUploadActivityPolling = (
teId: TeIdType
): UploadActivityPollingResult => {
const { dispatch, uploadActivityId }: StoreContextType = useAppContext();
const [fetchActivityStatus, { error: UploadActivityError, data: UploadActivityData, stopPolling }] = useLazyQuery(
GET_UPLOAD_ACTIVITY,
{
pollInterval: 3000,
fetchPolicy: 'network-only',
variables: { teId, activityId: uploadActivityId },
}
);
useEffect(() => {
if (UploadActivityData) {
setUploadActivityId(
UploadActivityData.getUploadActivityStatus.activity_id,
dispatch
);
updateActivityStateAction(UploadActivityData.getExcelUploadActivityStatus.status, dispatch);
}
}, [UploadActivityData]);
return { fetchActivityStatus, stopPolling, UploadActivityError };
};
import React from 'react';
import { mount } from 'enzyme';
const TestCustomHook = ({ callback }) => {
callback();
return null;
};
export const testCustomHook = callback => {
mount(<TestCustomHook callback={callback} />);
};
describe('useUploadActivityPolling', () => {
let pollingResult;
const teId = 'some id';
beforeEach(() => {
testCustomHook(() => {
pollingResult = useUploadActivityPolling(teId);
});
});
test('should have an fetchActivityStatus function', () => {
expect(pollingResult.fetchActivityStatus).toBeInstanceOf(Function);
});
});

Resources