Skip to content

Commit

Permalink
fix: extract implementation from context make
Browse files Browse the repository at this point in the history
  • Loading branch information
ascariandrea committed Jan 17, 2025
1 parent 46575c9 commit 5892232
Show file tree
Hide file tree
Showing 83 changed files with 1,166 additions and 694 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/packages-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ jobs:
pnpm core lint
pnpm shared lint
pnpm test lint
pnpm backend lint
pnpm ui lint
- name: Run tests
env:
DEBUG: "-@liexp*"
run: |
pnpm shared test
pnpm backend test
2 changes: 1 addition & 1 deletion packages/@liexp/backend/src/context/api.context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Endpoints } from "@liexp/shared/lib/endpoints/index.js";
import { type EndpointsRESTClient } from "@liexp/shared/lib/providers/EndpointsRESTClient/types";
import { type EndpointsRESTClient } from "@liexp/shared/lib/providers/EndpointsRESTClient/types.js";

export interface APIContext {
api: EndpointsRESTClient<Endpoints>;
Expand Down
5 changes: 0 additions & 5 deletions packages/@liexp/backend/src/context/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { type URLMetadataClient } from "../providers/URLMetadata.provider.js";
import { type FFMPEGProvider } from "../providers/ffmpeg/ffmpeg.provider.js";
import { type GeocodeProvider } from "../providers/geocode/geocode.provider.js";
import { type IGProvider } from "../providers/ig/ig.provider.js";
Expand All @@ -7,10 +6,6 @@ import { type NERProvider } from "../providers/ner/ner.provider.js";
import { type TGBotProvider } from "../providers/tg/tg.provider.js";
import { type WikipediaProvider } from "../providers/wikipedia/wikipedia.provider.js";

export interface URLMetadataContext {
urlMetadata: URLMetadataClient;
}

export interface FFMPEGProviderContext {
ffmpeg: FFMPEGProvider;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/@liexp/backend/src/context/urlMetadata.context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { URLMetadataClient } from "../providers/URLMetadata.provider.js";

export interface URLMetadataContext {
urlMetadata: URLMetadataClient;
}
2 changes: 1 addition & 1 deletion packages/@liexp/backend/src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ export * from "./NotAuthorizedError.js";
export * from "./NotFoundError.js";
export * from "./ServerError.js";

export { IOError } from "ts-io-error/lib/index.js";
export { IOError } from "ts-io-error";
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { fp, pipe } from "@liexp/core/lib/fp/index.js";
import { uuid } from "@liexp/shared/lib/io/http/Common/UUID.js";
import { SCIENTIFIC_STUDY } from "@liexp/shared/lib/io/http/Events/EventType.js";
import { HumanReadableStringArb } from "@liexp/shared/lib/tests/arbitrary/HumanReadableString.arbitrary.js";
import { throwTE } from "@liexp/shared/lib/utils/task.utils.js";
import { sanitizeURL } from "@liexp/shared/lib/utils/url.utils.js";
import { fc } from "@liexp/test";
import { describe, expect, it } from "vitest";
import { mockDeep } from "vitest-mock-extended";
import { EventV2Entity } from "../../entities/Event.v2.entity.js";
import { UserEntity } from "../../entities/User.entity.js";
import { initContext } from "../../test/index.js";
import { mockedContext, mockTERightOnce } from "../../test/mocks/mock.utils.js";
import {
createEventFromURL,
type CreateEventFromURLContext,
} from "./createEventFromURL.flow.js";

describe(createEventFromURL.name, () => {
const appTest = {
ctx: mockedContext<CreateEventFromURLContext>({
puppeteer: mockDeep(),
logger: mockDeep(),
db: mockDeep(),
ner: mockDeep(),
fs: mockDeep(),
urlMetadata: mockDeep(),
config: initContext().config,
}),
};

it("should create an event from a URL", async () => {
const [url] = fc
.sample(fc.nat(), 1)
.map((id) => `https://www.sciencedirect.com/article/${id}` as any);

const title = fc.sample(HumanReadableStringArb(), 1)[0];
const description = fc.sample(HumanReadableStringArb(), 1)[0];

// no url in db
mockTERightOnce(appTest.ctx.db.execQuery, () => null);
// event by url
mockTERightOnce(appTest.ctx.puppeteer.execute, () =>
fp.O.some({
type: SCIENTIFIC_STUDY.value,
date: new Date(),
payload: {
title,
url,
description,
},
}),
);

// link by url
let savedEvent: any;
mockTERightOnce(appTest.ctx.db.save, (_, event) => {
savedEvent = event[0];
return event;
});

mockTERightOnce(appTest.ctx.db.findOneOrFail, () => savedEvent);

// mocks.urlMetadata.fetchMetadata.mockResolvedValue({
// title,
// description,
// url: scientificStudyData.url,
// keywords: [],
// });

// mockTERightOnce(appTest.ctx.puppeteer.getBrowser, () => null);
// mocks.puppeteer.page.goto.mockResolvedValueOnce(undefined);

// // evaluate title
// mocks.puppeteer.page.$eval.mockResolvedValueOnce(title);
// // evaluate dropdown click
// mocks.puppeteer.page.click.mockResolvedValueOnce(undefined);
// // evaluate date string
// mocks.puppeteer.page.$eval.mockResolvedValueOnce([
// "Received 27 July 2020",
// "Accepted 1 August 2020",
// ]);
// // wait for
// mocks.puppeteer.page.waitForSelector.mockResolvedValueOnce(undefined);
// mocks.puppeteer.page.$$.mockResolvedValueOnce([
// {
// evaluate: vi.fn().mockResolvedValue(description),
// },
// ]);

// mocks.puppeteer.page.$eval.mockResolvedValueOnce("page content");

// mocks.ner.winkMethods.learnCustomEntities.mockResolvedValueOnce({} as any);
// mocks.ner.doc.out.mockReturnValue([]);
// mocks.ner.doc.sentences.mockReturnValue({ each: vi.fn() } as any);
// mocks.ner.doc.customEntities.mockReturnValue({
// out: vi.fn().mockReturnValue([]),
// } as any);
// mocks.ner.doc.tokens.mockReturnValue({ each: vi.fn() } as any);

// mocks.fs.existsSync.mockReturnValue(false);
// mocks.fs.readFileSync.mockReturnValue("[]");

const user = new UserEntity();

const event: any = await pipe(
createEventFromURL(
user,
uuid(),
url,
SCIENTIFIC_STUDY.value,
)(appTest.ctx),
throwTE,
);

expect(appTest.ctx.db.save).toHaveBeenCalledWith(EventV2Entity, [
expect.objectContaining({
type: SCIENTIFIC_STUDY.value,
payload: {
title,
description,
url: sanitizeURL(url),
},
}),
]);

expect(event.type).toBe(SCIENTIFIC_STUDY.value);
expect(event.date).toBe(savedEvent.date);

expect(event.payload).toMatchObject(savedEvent.payload);
});
});
24 changes: 11 additions & 13 deletions packages/@liexp/backend/src/flows/event/createEventFromURL.flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,25 @@ import { Equal } from "typeorm";
import { type ConfigContext } from "../../context/config.context.js";
import { type DatabaseContext } from "../../context/db.context.js";
import { type FSClientContext } from "../../context/fs.context.js";
import {
type NERProviderContext,
type URLMetadataContext,
} from "../../context/index.js";
import { type NERProviderContext } from "../../context/index.js";
import { type LoggerContext } from "../../context/logger.context.js";
import { type PuppeteerProviderContext } from "../../context/puppeteer.context.js";
import { type URLMetadataContext } from "../../context/urlMetadata.context.js";
import { EventV2Entity } from "../../entities/Event.v2.entity.js";
import { type UserEntity } from "../../entities/User.entity.js";
import { ServerError } from "../../errors/ServerError.js";
import { findByURL } from "../../queries/events/scientificStudy.query.js";
import { extractEventFromURL } from "./extractFromURL.flow.js";

export const createEventFromURL = <
C extends LoggerContext &
ConfigContext &
FSClientContext &
NERProviderContext &
DatabaseContext &
URLMetadataContext &
PuppeteerProviderContext,
>(
export type CreateEventFromURLContext = LoggerContext &
ConfigContext &
FSClientContext &
NERProviderContext &
DatabaseContext &
URLMetadataContext &
PuppeteerProviderContext;

export const createEventFromURL = <C extends CreateEventFromURLContext>(
user: UserEntity,
eventId: UUID,
url: URL,
Expand Down
135 changes: 135 additions & 0 deletions packages/@liexp/backend/src/flows/event/extractFromURL.flow.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { fp, pipe } from "@liexp/core/lib/fp/index.js";
import { uuid } from "@liexp/shared/lib/io/http/Common/UUID.js";
import { SCIENTIFIC_STUDY } from "@liexp/shared/lib/io/http/Events/EventType.js";
import { HumanReadableStringArb } from "@liexp/shared/lib/tests/arbitrary/HumanReadableString.arbitrary.js";
import { throwTE } from "@liexp/shared/lib/utils/task.utils.js";
import { sanitizeURL } from "@liexp/shared/lib/utils/url.utils.js";
import { fc } from "@liexp/test";
import { describe, expect, it, vi } from "vitest";
import { mock } from "vitest-mock-extended";
import { LinkEntity } from "../../entities/Link.entity.js";
import { UserEntity } from "../../entities/User.entity.js";
import { initContext, testConfig } from "../../test/index.js";
import { mockedContext, mockTERightOnce } from "../../test/mocks/mock.utils.js";
import { mocks } from "../../test/mocks.js";
import {
createEventFromURL,
type CreateEventFromURLContext,
} from "./createEventFromURL.flow.js";
import { extractEventFromURL } from "./extractFromURL.flow.js";

describe.skip(extractEventFromURL.name, () => {
const appTest = {
ctx: mockedContext<CreateEventFromURLContext>({
puppeteer: mock(),
logger: mock(),
db: mock(),
ner: mock(),
fs: mock(),
urlMetadata: mock(),
config: testConfig,
}),
};

it("should create an event from a URL", async () => {
const [url] = fc
.sample(fc.nat(), 1)
.map((id) => `https://www.sciencedirect.com/article/${id}` as any);

const title = fc.sample(HumanReadableStringArb(), 1)[0];
const description = fc.sample(HumanReadableStringArb(), 1)[0];

const scientificStudyData = { url };

// event by url
mockTERightOnce(appTest.ctx.db.execQuery, () => null);
// link by url
mockTERightOnce(appTest.ctx.db.findOne, () => null);
// save link
let savedEvent: any;
appTest.ctx.db.save.mockImplementation((_, link) => {
const l: any = link;
if (l[0].type === SCIENTIFIC_STUDY.value) {
savedEvent = l[0];
}
return fp.TE.right(link);
});

mockTERightOnce(appTest.ctx.db.findOneOrFail, () => savedEvent);

mocks.redis.publish.mockResolvedValue(1);
mocks.urlMetadata.fetchMetadata.mockResolvedValue({
title,
description,
url: scientificStudyData.url,
keywords: [],
});

mockTERightOnce(appTest.ctx.puppeteer.getBrowser, () => null);
mocks.puppeteer.page.goto.mockResolvedValueOnce(undefined);

// evaluate title
mocks.puppeteer.page.$eval.mockResolvedValueOnce(title);
// evaluate dropdown click
mocks.puppeteer.page.click.mockResolvedValueOnce(undefined);
// evaluate date string
mocks.puppeteer.page.$eval.mockResolvedValueOnce([
"Received 27 July 2020",
"Accepted 1 August 2020",
]);
// wait for
mocks.puppeteer.page.waitForSelector.mockResolvedValueOnce(undefined);
mocks.puppeteer.page.$$.mockResolvedValueOnce([
{
evaluate: vi.fn().mockResolvedValue(description),
},
]);

mocks.puppeteer.page.$eval.mockResolvedValueOnce("page content");

mocks.ner.winkMethods.learnCustomEntities.mockResolvedValueOnce({} as any);
mocks.ner.doc.out.mockReturnValue([]);
mocks.ner.doc.sentences.mockReturnValue({ each: vi.fn() } as any);
mocks.ner.doc.customEntities.mockReturnValue({
out: vi.fn().mockReturnValue([]),
} as any);
mocks.ner.doc.tokens.mockReturnValue({ each: vi.fn() } as any);

mocks.fs.existsSync.mockReturnValue(false);
mocks.fs.readFileSync.mockReturnValue("[]");

const user = new UserEntity();

const event: any = await pipe(
createEventFromURL(
user,
uuid(),
scientificStudyData.url,
SCIENTIFIC_STUDY.value,
)(appTest.ctx),
throwTE,
);

expect(mocks.ner).toHaveBeenCalledTimes(1);
expect(mocks.ner.winkMethods.learnCustomEntities).toHaveBeenCalledTimes(1);
expect(mocks.ner.winkMethods.readDoc).toHaveBeenCalledTimes(1);
expect(mocks.ner.doc.out).toHaveBeenCalledTimes(1);

expect(mocks.db.connection.manager.save).toHaveBeenCalledWith(
LinkEntity,

[
expect.objectContaining({
url: sanitizeURL(scientificStudyData.url),
}),
],
undefined,
);

expect(event.type).toBe(SCIENTIFIC_STUDY.value);
expect(event.date).toBeDefined();

expect(event.payload.url).toBeInstanceOf(String);
expect(event.payload.title).toEqual(title);
});
});
Loading

0 comments on commit 5892232

Please sign in to comment.