Compare commits
10 Commits
1.3.1
...
WEBINT-328
| Author | SHA1 | Date | |
|---|---|---|---|
| 031f5d5cf0 | |||
| 047669bab2 | |||
| 5ea5939e3a | |||
| 7ff3f13af4 | |||
| a9589f578b | |||
| a27e4b30d2 | |||
| 4168949b9e | |||
| e6ff54a15d | |||
| 3956797020 | |||
| 7d88359263 |
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Para la tarea WEBINT-328-Pausas-cacelaciones.
|
||||
* Almacena las pausas/cancelaciones que no se han podido hacer porque la linea esta en
|
||||
* "Test"
|
||||
*/
|
||||
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE SUSPENDTERMINATE AS ENUM ('suspend','terminate');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pause_cancel_tasks (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
iccid TEXT NOT NULL,
|
||||
operation_type SUSPENDTERMINATE,
|
||||
last_checked TIMESTAMPTZ, -- Última vez que se ha comprobado que no esté en test
|
||||
activation_date TIMESTAMPTZ, -- Fecha de activacion para comprobar si ha pasdo un mes
|
||||
next_check TIMESTAMPTZ, -- Si se ha comprobado se asignará la siguiente fecha de revision
|
||||
|
||||
completed_date TIMESTAMPTZ, -- Cuando se ha completado, para bien o mal.
|
||||
error TEXT,
|
||||
action_data JSONB -- datos de la operacion original.
|
||||
);
|
||||
|
||||
-- Indice de las tareas que no han terminado
|
||||
CREATE INDEX idx_pause_cancel_tasks_pending
|
||||
ON pause_cancel_tasks (next_check)
|
||||
WHERE completed_date IS NULL;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ params:query {
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201125065160414
|
||||
iccid: 8933201125068886692
|
||||
}
|
||||
|
||||
settings {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
vars {
|
||||
baseurl: http://localhost:3000
|
||||
}
|
||||
color: #2E8A54
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
vars {
|
||||
baseurl: https://sf-sims.savefamilygps.net
|
||||
}
|
||||
color: #CE4F3B
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { describe, it, beforeEach, mock, after } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { SimController } from "./Sim.controller.js";
|
||||
import { EventBus } from "sim-shared/domain/EventBus.port.js";
|
||||
import { SimUseCases } from "./Sim.usecases.js";
|
||||
import { ConsumeMessage } from "amqplib";
|
||||
import { postgrClient, pgPool } from "#config/postgreConfig.js";
|
||||
import { httpInstance } from "#config/httpClient.config.js";
|
||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
||||
import { PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js";
|
||||
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js";
|
||||
import { ActionData } from "#domain/DTOs/objeniousapi.js";
|
||||
|
||||
describe("SimController Integration Tests (Real UseCases)", () => {
|
||||
let eventBusMock: any;
|
||||
let controller: SimController;
|
||||
let useCases: SimUseCases;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock ONLY the event bus as requested
|
||||
eventBusMock = {
|
||||
publish: mock.fn(),
|
||||
addSubscribers: mock.fn(),
|
||||
consume: mock.fn(),
|
||||
ack: mock.fn(async () => { }),
|
||||
nack: mock.fn(async () => { }),
|
||||
};
|
||||
|
||||
const operationRepository = new ObjeniousOperationsRepository(
|
||||
httpInstance,
|
||||
postgrClient,
|
||||
);
|
||||
const orderRepository = new OrderRepository(postgrClient);
|
||||
const pauseRepository = new PauseCancelTaskRepository(postgrClient);
|
||||
useCases = new SimUseCases({
|
||||
httpClient: httpInstance,
|
||||
operationRepository: operationRepository,
|
||||
orderRepository: orderRepository,
|
||||
pauseRepository: pauseRepository
|
||||
});
|
||||
// @ts-expect-error
|
||||
useCases.findActivationDate = async (data: ActionData) => new Date()
|
||||
|
||||
controller = new SimController(eventBusMock as unknown as EventBus, useCases);
|
||||
});
|
||||
|
||||
const createMockMsg = (payload: any): ConsumeMessage => {
|
||||
return {
|
||||
content: Buffer.from(JSON.stringify(payload)),
|
||||
fields: {},
|
||||
properties: {
|
||||
headers: {
|
||||
message_id: "test-correlation-id"
|
||||
}
|
||||
},
|
||||
} as unknown as ConsumeMessage;
|
||||
};
|
||||
|
||||
after(async () => {
|
||||
await pgPool.end();
|
||||
});
|
||||
|
||||
describe("suspend", () => {
|
||||
it("should call stage_suspend and interact with DB and EventBus", async () => {
|
||||
const iccid = "test-iccid-suspend-" + Date.now();
|
||||
const msg = createMockMsg({
|
||||
key: "sim.test.pause",
|
||||
payload: {
|
||||
iccid: iccid
|
||||
},
|
||||
headers: {
|
||||
message_id: "correlation-suspend-" + iccid
|
||||
}
|
||||
});
|
||||
|
||||
const handler = controller.suspend();
|
||||
await handler(msg);
|
||||
|
||||
// Verify that it reached the stage_suspend logic (which adds to pauseRepository)
|
||||
// We can query the DB or check if ACK was called
|
||||
assert.strictEqual(eventBusMock.ack.mock.callCount(), 1, "Message should be ACKed on success");
|
||||
assert.strictEqual(eventBusMock.nack.mock.callCount(), 0, "Message should not be NACKed");
|
||||
});
|
||||
});
|
||||
|
||||
describe("terminate", () => {
|
||||
it("should call stage_terminate and interact with DB and EventBus", async () => {
|
||||
const iccid = "test-iccid-terminate-" + Date.now();
|
||||
const msg = createMockMsg({
|
||||
key: "sim.test.pause",
|
||||
payload: {
|
||||
iccid: iccid
|
||||
},
|
||||
headers: {
|
||||
message_id: "correlation-terminate-" + iccid
|
||||
}
|
||||
});
|
||||
|
||||
const handler = controller.terminate();
|
||||
await handler(msg);
|
||||
|
||||
assert.strictEqual(eventBusMock.ack.mock.callCount(), 1, "Message should be ACKed on success");
|
||||
assert.strictEqual(eventBusMock.nack.mock.callCount(), 0, "Message should not be NACKed");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Error Handling", () => {
|
||||
it("should nack if message is invalid", async () => {
|
||||
const msg = {
|
||||
content: Buffer.from("invalid json"),
|
||||
fields: {},
|
||||
properties: {},
|
||||
} as unknown as ConsumeMessage;
|
||||
const handler = controller.suspend();
|
||||
await assert.rejects(handler(msg), "Error de suspension consumiendo el mensaje no es valido");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { ConsumeMessage } from "amqplib";
|
||||
import { SimUseCases } from "./Sim.usecases.js";
|
||||
import { SimEvents } from "sim-shared/domain/SimEvents.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { env } from "#config/env/index.js";
|
||||
import { ActionData } from "#domain/DTOs/objeniousapi.js";
|
||||
|
||||
/**
|
||||
* La clase usa generadores de funciones para mantener el contexto
|
||||
@@ -37,6 +37,7 @@ export class SimController {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error al decodificar JSON:', error);
|
||||
console.error(Buffer.from(msg.content).toString(("utf8")))
|
||||
// Aquí podrías decidir devolver el string crudo o null
|
||||
return undefined;
|
||||
}
|
||||
@@ -157,6 +158,9 @@ export class SimController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lo mismo que pause
|
||||
*/
|
||||
public suspend() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
let msgData;
|
||||
@@ -171,14 +175,18 @@ export class SimController {
|
||||
}
|
||||
|
||||
const iccid = msgData.payload.iccid
|
||||
const res = await this.tryUseCase(msg, this.useCases.suspend({
|
||||
const suspendData: ActionData = {
|
||||
correlation_id: msgData.headers?.message_id,
|
||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||
identifier: {
|
||||
identifierType: "ICCID",
|
||||
identifiers: [iccid]
|
||||
identifiers: [iccid] // Por algún motivo solo he puesto un iccd por identifier
|
||||
}
|
||||
}))
|
||||
}
|
||||
const useCaseRes = await this.tryUseCase(msg, this.useCases.stage_suspend(suspendData))
|
||||
/*
|
||||
const res = await this.tryUseCase(msg, this.useCases.suspend(actionData))
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
@@ -195,16 +203,20 @@ export class SimController {
|
||||
if (msgData == undefined) {
|
||||
return Promise.reject("Mensaje invalido")
|
||||
}
|
||||
|
||||
const iccid = msgData.payload.iccid
|
||||
console.log("Mensaje procesado", msgData)
|
||||
const res = await this.tryUseCase(msg, this.useCases.terminate({
|
||||
const terminateActionData: ActionData = {
|
||||
correlation_id: msgData.headers?.message_id,
|
||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||
identifier: {
|
||||
identifierType: "ICCID",
|
||||
identifiers: [iccid]
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
//const res = await this.tryUseCase(msg, this.useCases.terminate(terminateActionData))
|
||||
const res = await this.tryUseCase(msg, this.useCases.stage_terminate(terminateActionData))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import { Result } from "sim-shared/domain/Result.js"
|
||||
import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort } from "sim-shared/domain/operationsRepository.port.js"
|
||||
import assert from "node:assert"
|
||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
||||
import { CreatePauseCancelTaskDTO, PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js"
|
||||
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js"
|
||||
|
||||
// TODO:
|
||||
// - Pasar a un archivo de DTOs
|
||||
@@ -12,21 +14,24 @@ import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
||||
|
||||
export class SimUseCases {
|
||||
private readonly httpClient: HttpClient
|
||||
private readonly operationRepository: OperationsRepositoryPort
|
||||
private readonly objeniousRepository: ObjeniousOperationsRepository
|
||||
private readonly orderRepository: OrderRepository
|
||||
private readonly pauseRepository: PauseCancelTaskRepository
|
||||
|
||||
constructor(args: {
|
||||
httpClient: HttpClient,
|
||||
operationRepository: OperationsRepositoryPort,
|
||||
orderRepository: OrderRepository
|
||||
operationRepository: ObjeniousOperationsRepository,
|
||||
orderRepository: OrderRepository,
|
||||
pauseRepository: PauseCancelTaskRepository
|
||||
}) {
|
||||
this.httpClient = args.httpClient
|
||||
this.operationRepository = args.operationRepository
|
||||
this.objeniousRepository = args.operationRepository
|
||||
this.orderRepository = args.orderRepository
|
||||
this.pauseRepository = args.pauseRepository
|
||||
}
|
||||
|
||||
private async logOperation(data: ObjeniousOperation) {
|
||||
await this.operationRepository.createOperation({
|
||||
await this.objeniousRepository.createOperation({
|
||||
...data
|
||||
})
|
||||
}
|
||||
@@ -73,6 +78,8 @@ export class SimUseCases {
|
||||
request_id: response.data.requestId
|
||||
}
|
||||
|
||||
// TODO: Esto tiene poco sentido si la operacion ya se
|
||||
// tenia que haber creado en el generador
|
||||
this.logOperation(operation)
|
||||
.then().catch(e => console.error(e))
|
||||
|
||||
@@ -89,7 +96,6 @@ export class SimUseCases {
|
||||
error: undefined,
|
||||
data: true
|
||||
}
|
||||
|
||||
} else {
|
||||
return {
|
||||
error: String(response.status),
|
||||
@@ -238,6 +244,133 @@ export class SimUseCases {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Metodo muy especifico para obtener la fecha e activacion o en su defecto
|
||||
* la actual para aber cuando se va a completar el periodo de test de una linea
|
||||
*/
|
||||
private async findActivationDate(actionData: ActionData) {
|
||||
const iccid = actionData.identifier.identifiers
|
||||
const lineData = await this.objeniousRepository.getLinesAPI("ICCID", iccid)
|
||||
let activationDate = new Date()
|
||||
// Si no se pueden sacar datos de la linea guardo momentaneamente el error
|
||||
// pero no se cancela la operacion, el error puede ser de objenious y no nos
|
||||
// puede afectar
|
||||
console.log("LineData", lineData.data)
|
||||
if (lineData.error != undefined) {
|
||||
console.error(lineData.error)
|
||||
} else {
|
||||
const activationDateStr = lineData.data[0].status.activationDate
|
||||
if (activationDateStr != undefined && activationDateStr != "") {
|
||||
activationDate = new Date(activationDateStr)
|
||||
}
|
||||
}
|
||||
return activationDate
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paso previo a la suspension para evitar errores cuando el billing es test
|
||||
*/
|
||||
public stage_suspend(suspendData: ActionData): () => Promise<Result<string, boolean>> {
|
||||
return async (): Promise<Result<string, boolean>> => {
|
||||
const correlation_id = suspendData.correlation_id
|
||||
const iccid = suspendData.identifier.identifiers
|
||||
|
||||
const fail = (error: string) => {
|
||||
console.error("[Sim.usecases]", error)
|
||||
if (correlation_id != undefined) {
|
||||
this.orderRepository.updateOrder({
|
||||
correlation_id: correlation_id,
|
||||
new_status: "failed"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let activationDate;
|
||||
try {
|
||||
activationDate = await this.findActivationDate(suspendData)
|
||||
} catch (e) {
|
||||
return {
|
||||
error: String(e)
|
||||
}
|
||||
}
|
||||
const newTask: CreatePauseCancelTaskDTO = {
|
||||
iccid: iccid[0],
|
||||
activation_date: activationDate,
|
||||
next_check: undefined, // Que se haga instantaneamente al ser la primera
|
||||
operation_type: "suspend",
|
||||
action_data: suspendData
|
||||
}
|
||||
|
||||
const taskCreated = await this.pauseRepository.addTask(newTask)
|
||||
|
||||
// Caso que la task no se pueda crear en la BDD
|
||||
if (taskCreated.error != undefined) {
|
||||
fail(taskCreated.error)
|
||||
return {
|
||||
error: taskCreated.error
|
||||
}
|
||||
}
|
||||
|
||||
// Caso que se haya creado en la BDD
|
||||
if (correlation_id != undefined) {
|
||||
this.orderRepository.updateOrder({
|
||||
correlation_id: correlation_id,
|
||||
new_status: "running"
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
data: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paso previo a la suspension para evitar errores cuando el billing es test
|
||||
*/
|
||||
public stage_terminate(terminateData: ActionData): () => Promise<Result<string, boolean>> {
|
||||
return async (): Promise<Result<string, boolean>> => {
|
||||
const correlation_id = terminateData.correlation_id
|
||||
|
||||
const activationDate = await this.findActivationDate(terminateData)
|
||||
const newTask: CreatePauseCancelTaskDTO = {
|
||||
iccid: terminateData.identifier.identifiers[0],
|
||||
activation_date: activationDate,
|
||||
next_check: undefined, // Que se haga instantaneamente al ser la primera
|
||||
operation_type: "terminate",
|
||||
action_data: terminateData
|
||||
}
|
||||
|
||||
const taskCreated = await this.pauseRepository.addTask(newTask)
|
||||
|
||||
// Caso que la task no se pueda crear en la BDD
|
||||
if (taskCreated.error != undefined) {
|
||||
console.error("[Sim.usecases]", taskCreated.error)
|
||||
if (correlation_id != undefined) {
|
||||
this.orderRepository.updateOrder({
|
||||
correlation_id: correlation_id,
|
||||
new_status: "failed"
|
||||
})
|
||||
}
|
||||
return {
|
||||
error: taskCreated.error
|
||||
}
|
||||
}
|
||||
|
||||
// Caso que se haya creado en la BDD
|
||||
if (correlation_id != undefined) {
|
||||
this.orderRepository.updateOrder({
|
||||
correlation_id: correlation_id,
|
||||
new_status: "running"
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
data: true
|
||||
}
|
||||
}
|
||||
}
|
||||
public terminate(terminationData: ActionData): () => Promise<Result<string, boolean>> {
|
||||
const OPERATION_URL = "/actions/terminateLine"
|
||||
return this.generateUseCase({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
|
||||
import { JWTService } from "../aplication/JWT.service.js"
|
||||
import { env } from "./env/index.js"
|
||||
import { jwtService } from "./jwtService.config.js"
|
||||
|
||||
const OBJ_BASE_URL = env.OBJ_BASE_URL
|
||||
|
||||
@@ -9,5 +9,5 @@ export const httpInstance = new HttpClient({
|
||||
headers: {
|
||||
"content-type": " application/json; charset=utf-8"
|
||||
},
|
||||
jwtManager: new JWTService()
|
||||
jwtManager: jwtService
|
||||
})
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
|
||||
import { env } from "./env/index.js"
|
||||
import { JWTHeader } from "sim-shared/domain/JWT.js"
|
||||
|
||||
|
||||
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
||||
|
||||
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
||||
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
||||
|
||||
const DEFAULT_BODY: GrantAccessRequestBody = {
|
||||
grant_type: "client_credentials",
|
||||
client_id: env.OBJ_CLIENT_ID,
|
||||
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
client_assertion: env.OBJ_CLI_ASSERTION
|
||||
}
|
||||
|
||||
|
||||
const DEFAULT_HEADERS = {
|
||||
"content-type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
const DEFAULT_HEADERS_JWT = {
|
||||
alg: "RS256",
|
||||
typ: "JWT",
|
||||
kid: env.OBJ_KID,
|
||||
}
|
||||
|
||||
const DEFAULT_DATA_JWT = {
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||
jti: Date.now().toString(),
|
||||
|
||||
}
|
||||
|
||||
function addIATHeaders(authHeaders: Object) {
|
||||
const headers = <JWTHeader>{
|
||||
...authHeaders,
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: GET_TOKEN_URL,
|
||||
jti: Date.now().toString(),
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
export const jwtService = new JWTService({
|
||||
transformJWTHeaders: addIATHeaders,
|
||||
defaultHeaders: DEFAULT_HEADERS,
|
||||
defaultBody: DEFAULT_BODY,
|
||||
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
|
||||
defaultJWTPayload: DEFAULT_DATA_JWT,
|
||||
privateKeyPath: PRIVATE_KEY_PATH,
|
||||
tokenUrl: GET_TOKEN_URL,
|
||||
refreshTokenUrl: REFRESH_TOKEN_URL
|
||||
})
|
||||
@@ -8,6 +8,7 @@ import { SimUseCases } from "./aplication/Sim.usecases.js"
|
||||
import { SimController } from "./aplication/Sim.controller.js"
|
||||
import { SimRouter } from "./aplication/Sim.router.js"
|
||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
||||
import { PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js"
|
||||
|
||||
async function startWorker() {
|
||||
const rmqClient = await startRMQClient()
|
||||
@@ -18,15 +19,21 @@ async function startWorker() {
|
||||
|
||||
await pgClient.checkDatabaseConnection()
|
||||
|
||||
const operationRepository = new ObjeniousOperationsRepository(pgClient)
|
||||
const operationRepository = new ObjeniousOperationsRepository(
|
||||
httpClient,
|
||||
pgClient,
|
||||
)
|
||||
const orderRepository = new OrderRepository(pgClient)
|
||||
|
||||
const pauseRepository = new PauseCancelTaskRepository(pgClient)
|
||||
|
||||
const simActivationController = new SimController(
|
||||
rmqClient,
|
||||
new SimUseCases({
|
||||
httpClient: httpClient,
|
||||
operationRepository: operationRepository,
|
||||
orderRepository: orderRepository
|
||||
orderRepository: orderRepository,
|
||||
pauseRepository: pauseRepository
|
||||
})
|
||||
)
|
||||
const simRouter = new SimRouter(simActivationController, rmqClient)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { after, before, describe, it } from "node:test";
|
||||
import { CreatePauseCancelTaskDTO, PauseCancelTaskRepository } from "./PauseCancelTaskRepository.js";
|
||||
import { postgrClient } from "#config/postgreConfig.js";
|
||||
import assert from "node:assert";
|
||||
|
||||
const testTask: CreatePauseCancelTaskDTO = {
|
||||
iccid: "1234",
|
||||
operation_type: "suspend",
|
||||
activation_date: new Date(),
|
||||
next_check: new Date(),
|
||||
action_data: {
|
||||
dueDate: new Date().toString(),
|
||||
correlation_id: "12223",
|
||||
identifier: {
|
||||
identifiers: ["1234"],
|
||||
identifierType: "ICCID"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("Test PauseCancelTaskRepository - DB", () => {
|
||||
|
||||
const createdIds: number[] = [];
|
||||
const pauseRepo = new PauseCancelTaskRepository(postgrClient)
|
||||
|
||||
before(() => {
|
||||
})
|
||||
|
||||
after(() => {
|
||||
})
|
||||
|
||||
it("Should create a task", async () => {
|
||||
const created = await pauseRepo.addTask(testTask)
|
||||
assert.ok(created != undefined, "A value must be returned always")
|
||||
assert.ok(created.error == undefined, "Should not return a error")
|
||||
assert.ok(created.data != undefined, "Data must be returned")
|
||||
createdIds.push(created.data.id)
|
||||
})
|
||||
|
||||
it("Should update a existing task", async () => {
|
||||
const updated = await pauseRepo.updateTask({
|
||||
id: createdIds[0],
|
||||
next_check: new Date()
|
||||
})
|
||||
|
||||
assert.ok(updated != undefined, "A value must be returned always")
|
||||
assert.ok(updated.error == undefined, "Should not return a error")
|
||||
assert.ok(updated.data != undefined, "Data must be returned")
|
||||
})
|
||||
|
||||
it("Should finish a existing task", async () => {
|
||||
const finish = await pauseRepo.finishTask({
|
||||
id: createdIds[0],
|
||||
error: "ok"
|
||||
})
|
||||
|
||||
assert.ok(finish != undefined, "A value must be returned always")
|
||||
assert.ok(finish.error == undefined, "Should not return a error")
|
||||
assert.ok(finish.data != undefined, "Data must be returned")
|
||||
})
|
||||
|
||||
it("Should get at least 1 pending task", async () => {
|
||||
const created = await pauseRepo.addTask(testTask)
|
||||
const pending = await pauseRepo.getPending()
|
||||
|
||||
assert.ok(pending != undefined, "A value must be returned always")
|
||||
assert.ok(pending.error == undefined, "Should not return a error")
|
||||
assert.ok(pending.data != undefined, "Data must be returned")
|
||||
|
||||
console.log("--> ", pending.data[0])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { QueryResult } from "pg";
|
||||
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
|
||||
import { AxiosError } from "axios";
|
||||
import { ActionData } from "#domain/DTOs/objeniousapi.js";
|
||||
|
||||
export type PauseCancelTask = {
|
||||
id: number;
|
||||
iccid: string;
|
||||
operation_type: "suspend" | "terminate",
|
||||
last_checked?: Date | null;
|
||||
activation_date?: Date | null;
|
||||
next_check?: Date | null;
|
||||
completed_date?: Date | null;
|
||||
error?: string | null;
|
||||
action_data: ActionData
|
||||
}
|
||||
|
||||
export type CreatePauseCancelTaskDTO = Pick<PauseCancelTask, "iccid" | "activation_date" | "next_check" | "operation_type" | "action_data">
|
||||
export type UpdatePauseCancelTaskDTO = Pick<PauseCancelTask, "id" | "next_check">
|
||||
export type FinishPauseCancelTaskDTO = Pick<PauseCancelTask, "id" | "error">
|
||||
|
||||
/**
|
||||
* Repositorio para compensar los problemas de cacelcaiones/pausas de objenious a
|
||||
* la hora aplicarlo sobre una linea con el billing a test.
|
||||
*/
|
||||
export class PauseCancelTaskRepository {
|
||||
constructor(
|
||||
private readonly pgClient: PgClient
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene las siguientes que se pueden lanzar, puede haber más pero
|
||||
* estan pendientes
|
||||
*/
|
||||
public async getPending(): Promise<Result<string, PauseCancelTask[]>> {
|
||||
const sql = `
|
||||
SELECT * FROM pause_cancel_tasks
|
||||
WHERE completed_date IS NULL
|
||||
AND (next_check <= NOW() OR next_check IS NULL)
|
||||
ORDER BY id ASC;
|
||||
`;
|
||||
|
||||
try {
|
||||
const res: QueryResult<PauseCancelTask> = await this.pgClient.query(sql);
|
||||
return {
|
||||
data: res.rows
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
error: (e as AxiosError).message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async addTask(task: CreatePauseCancelTaskDTO): Promise<Result<string, PauseCancelTask>> {
|
||||
|
||||
const sql = `
|
||||
INSERT INTO pause_cancel_tasks (iccid, activation_date, next_check, last_checked, operation_type, actionData)
|
||||
VALUES ($1, $2, $3, now(), $4, $5)
|
||||
RETURNING *;
|
||||
`;
|
||||
try {
|
||||
const values = [task.iccid, task.activation_date, task.next_check, task.operation_type, JSON.stringify(task.action_data)];
|
||||
const res: QueryResult<PauseCancelTask> = await this.pgClient.query(sql, values);
|
||||
return {
|
||||
data: res.rows[0]
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
error: (e as AxiosError).message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Se ha vuelto a comprobar la tarea pero sigue en test
|
||||
*/
|
||||
public async updateTask(updateData: UpdatePauseCancelTaskDTO): Promise<Result<string, PauseCancelTask>> {
|
||||
|
||||
const sql = `
|
||||
UPDATE pause_cancel_tasks
|
||||
SET last_checked = now(), next_check = $1
|
||||
WHERE id = $2
|
||||
RETURNING *;
|
||||
`;
|
||||
try {
|
||||
const res = await this.pgClient.query<PauseCancelTask>(sql, [updateData.next_check, updateData.id]);
|
||||
return {
|
||||
data: res.rows[0]
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
error: (e as AxiosError).message
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* La tarea ha termiando bien o mal
|
||||
*/
|
||||
public async finishTask(finishData: FinishPauseCancelTaskDTO) {
|
||||
const sql = `
|
||||
UPDATE pause_cancel_tasks
|
||||
SET completed_date = NOW(), error = $1
|
||||
WHERE id = $2
|
||||
RETURNING *;
|
||||
`;
|
||||
|
||||
try {
|
||||
const res = await this.pgClient.query(sql, [finishData.error, finishData.id]);
|
||||
return {
|
||||
data: res.rows[0]
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
error: (e as AxiosError).message
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -53,7 +53,7 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test": "node --import tsx --test ./**/*.test.ts",
|
||||
"dev": "tsx watch index.ts",
|
||||
"build": "tsc --build && yarn tsc-alias -p tsconfig.json && cp .env package.json ../../dist/packages/sim-consumidor-objenious/",
|
||||
"start": "node ../../dist/packages/sim-consumidor-objenious/index.js",
|
||||
|
||||
@@ -36,7 +36,6 @@ export class SimController {
|
||||
}) {
|
||||
return async (req: Request, res: Response) => {
|
||||
const body = req.body
|
||||
|
||||
// 1. Validacion del body
|
||||
if (args.validator != undefined) {
|
||||
const validationResult = args.validator.validate(body)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
|
||||
import { env } from "./env/index.js"
|
||||
import { JWTService } from "packages/sim-consumidor-objenious/aplication/JWT.service.js"
|
||||
import { jwtService } from "./jwtService.config.js"
|
||||
|
||||
|
||||
const OBJ_BASE_URL = env.OBJ_BASE_URL
|
||||
|
||||
@@ -9,5 +10,5 @@ export const httpInstance = new HttpClient({
|
||||
headers: {
|
||||
"content-type": " application/json; charset=utf-8"
|
||||
},
|
||||
jwtManager: new JWTService()
|
||||
jwtManager: jwtService
|
||||
})
|
||||
|
||||
59
packages/sim-objenious-cron/config/jwtService.config.ts
Normal file
59
packages/sim-objenious-cron/config/jwtService.config.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
|
||||
import { env } from "./env/index.js"
|
||||
import { JWTHeader } from "sim-shared/domain/JWT.js"
|
||||
|
||||
|
||||
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
||||
|
||||
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
||||
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
||||
|
||||
const DEFAULT_BODY: GrantAccessRequestBody = {
|
||||
grant_type: "client_credentials",
|
||||
client_id: env.OBJ_CLIENT_ID,
|
||||
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
client_assertion: env.OBJ_CLI_ASSERTION
|
||||
}
|
||||
|
||||
|
||||
const DEFAULT_HEADERS = {
|
||||
"content-type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
const DEFAULT_HEADERS_JWT = {
|
||||
alg: "RS256",
|
||||
typ: "JWT",
|
||||
kid: env.OBJ_KID,
|
||||
}
|
||||
|
||||
const DEFAULT_DATA_JWT = {
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||
jti: Date.now().toString(),
|
||||
|
||||
}
|
||||
|
||||
function addIATHeaders(authHeaders: Object) {
|
||||
const headers = <JWTHeader>{
|
||||
...authHeaders,
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: GET_TOKEN_URL,
|
||||
jti: Date.now().toString(),
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
export const jwtService = new JWTService({
|
||||
transformJWTHeaders: addIATHeaders,
|
||||
defaultHeaders: DEFAULT_HEADERS,
|
||||
defaultBody: DEFAULT_BODY,
|
||||
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
|
||||
defaultJWTPayload: DEFAULT_DATA_JWT,
|
||||
privateKeyPath: PRIVATE_KEY_PATH,
|
||||
tokenUrl: GET_TOKEN_URL,
|
||||
refreshTokenUrl: REFRESH_TOKEN_URL
|
||||
})
|
||||
@@ -8,6 +8,9 @@ import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
||||
import { TaskVolcadoLineas } from "./tasks/volcado_lineas.js"
|
||||
import { ObjeniousLinesRepository } from "./infranstructure/ObjeniousLinesRepository.js"
|
||||
import { postgresClientIntranet } from "./config/intranetPostgresConfig.js"
|
||||
import { PauseCancelTaskRepository } from "packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.js"
|
||||
import { PauseTerminateTask } from "./tasks/check_pause_terminate.js"
|
||||
import { SimUseCases } from "packages/sim-consumidor-objenious/aplication/Sim.usecases.js"
|
||||
|
||||
async function startCron() {
|
||||
const commonSettings = {
|
||||
@@ -21,7 +24,10 @@ async function startCron() {
|
||||
console.log("[i] Comprobando conexion con la BDD ")
|
||||
await pgClient.checkDatabaseConnection()
|
||||
|
||||
const operationRepository = new ObjeniousOperationsRepository(pgClient)
|
||||
const operationRepository = new ObjeniousOperationsRepository(
|
||||
httpClient,
|
||||
pgClient,
|
||||
)
|
||||
const orderRepository = new OrderRepository(pgClient)
|
||||
const objeniousLineRepository = new ObjeniousLinesRepository(postgresClientIntranet)
|
||||
|
||||
@@ -31,7 +37,30 @@ async function startCron() {
|
||||
httpClient,
|
||||
)
|
||||
|
||||
const volcadoLineasTask = new TaskVolcadoLineas(httpClient, objeniousLineRepository)
|
||||
const objeniosRepo = new ObjeniousOperationsRepository(
|
||||
httpClient,
|
||||
pgClient
|
||||
)
|
||||
|
||||
const volcadoLineasTask = new TaskVolcadoLineas(
|
||||
objeniousLineRepository,
|
||||
objeniosRepo
|
||||
)
|
||||
|
||||
const pauseRepo = new PauseCancelTaskRepository(pgClient)
|
||||
const simUsecases = new SimUseCases({
|
||||
httpClient: httpClient,
|
||||
operationRepository: operationRepository,
|
||||
orderRepository: orderRepository,
|
||||
pauseRepository: pauseRepo
|
||||
})
|
||||
|
||||
const pauseTask = new PauseTerminateTask(
|
||||
objeniosRepo,
|
||||
pauseRepo,
|
||||
simUsecases,
|
||||
orderRepository
|
||||
)
|
||||
|
||||
const PERIODO_PETICIONES = 10 * 60 * 1000
|
||||
const interval = setInterval(async () => {
|
||||
@@ -51,7 +80,12 @@ async function startCron() {
|
||||
}
|
||||
}, PERIODO_VOLCADO)
|
||||
|
||||
await volcadoLineasTask.loadLines()
|
||||
|
||||
await pauseTask.run()
|
||||
const PERIODO_CANCELACIONES = 60 * 60 * 1000;
|
||||
const clacelacionesInterval = setInterval(async () => {
|
||||
await pauseTask.run()
|
||||
}, PERIODO_CANCELACIONES)
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@ import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
||||
import axios from "axios";
|
||||
import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-shared/domain/operationsRepository.port.js";
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
import { ObjeniousOperationsRepository } from "packages/sim-shared/infrastructure/ObjeniousOperationRepository.js";
|
||||
|
||||
export class CheckObjeniousRequests {
|
||||
constructor(
|
||||
private readonly operationsRepository: IOperationsRepository,
|
||||
private readonly operationsRepository: ObjeniousOperationsRepository,
|
||||
private readonly orderRepository: OrderRepository,
|
||||
private readonly httpClient: HttpClient
|
||||
) {
|
||||
|
||||
189
packages/sim-objenious-cron/tasks/check_pause_terminate.ts
Normal file
189
packages/sim-objenious-cron/tasks/check_pause_terminate.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { ObjeniousLine } from "sim-shared/domain/objeniousLine.js";
|
||||
import { PauseCancelTaskRepository } from "sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.js";
|
||||
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js";
|
||||
import { SimUseCases } from "sim-consumidor-objenious/aplication/Sim.usecases.js";
|
||||
import { OrderRepository } from "packages/sim-shared/infrastructure/OrderRepository.js";
|
||||
|
||||
const logger =
|
||||
{
|
||||
log: (...data: any[]) => console.log("[i] [TaskPauseTerminate]", ...data),
|
||||
error: (...data: any[]) => console.error("[x] [TaskPauseTerminate] ", ...data),
|
||||
}
|
||||
|
||||
|
||||
export class PauseTerminateTask {
|
||||
constructor(
|
||||
private readonly objeniousRepo: ObjeniousOperationsRepository,
|
||||
private readonly pauseRepo: PauseCancelTaskRepository,
|
||||
private readonly simUsecases: SimUseCases,
|
||||
private readonly orderRepo: OrderRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public async run() {
|
||||
const finError = (err: any) => {
|
||||
logger.error("Finalizado con errores proceso de comprobacion de lineas en pausa o canceladas")
|
||||
logger.error(err)
|
||||
}
|
||||
|
||||
const finExito = () => {
|
||||
logger.log("Finalizado con exito proceso de comprobacion de lineas en pausa o canceladas")
|
||||
}
|
||||
try {
|
||||
logger.log("Iniciando proceso de comprobacion de lineas en pausa o canceladas")
|
||||
|
||||
// 1. Se comprueba cuantas peticiones hay qye revisar
|
||||
const peticionesRevisar = await this.pauseRepo.getPending()
|
||||
|
||||
if (peticionesRevisar.error != undefined) {
|
||||
finError(peticionesRevisar.error)
|
||||
return 1;
|
||||
}
|
||||
|
||||
logger.log(`Se van a revisar ${peticionesRevisar.data?.length} peticiones`)
|
||||
if (peticionesRevisar.data == undefined || peticionesRevisar.data.length == 0) {
|
||||
finExito()
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// 2. Se comprueba que alguna de las lineas haya dejado de estar en estado de test
|
||||
const iccids = peticionesRevisar.data.map(e => e.iccid)
|
||||
const lineasActualizadas: ObjeniousLine[] = []
|
||||
|
||||
const lineGenerator = this.objeniousRepo.getLinesByStatusAPI({
|
||||
iccids: iccids
|
||||
})
|
||||
|
||||
let lines = await lineGenerator.next()
|
||||
|
||||
if (lines.value.error != undefined || lines.value.data == undefined) {
|
||||
logger.error("Error cargando las lineas", lines.value.error)
|
||||
finError(lines.value.error)
|
||||
return 1;
|
||||
} else {
|
||||
lineasActualizadas.push(...lines.value.data)
|
||||
}
|
||||
|
||||
while (!lines.done) {
|
||||
if (lines.value.error != undefined || lines.value.data == undefined) {
|
||||
logger.error("Error cargando las lineas", lines.value.error)
|
||||
finError(lines.value.error)
|
||||
return 1;
|
||||
} else {
|
||||
lineasActualizadas.push(...lines.value.data)
|
||||
}
|
||||
|
||||
lines = await lineGenerator.next()
|
||||
}
|
||||
|
||||
console.log("Cargado: ", lineasActualizadas)
|
||||
|
||||
// 3. Se separan las lineas que se tienen que actualizar al no ser test
|
||||
// y las que se tienen que reencolar al ser test
|
||||
const lineasNoTest = lineasActualizadas.filter(e => e.status.billingStatus != "TEST")
|
||||
const lineasTest = lineasActualizadas.filter(e => e.status.billingStatus == "TEST")
|
||||
|
||||
// 4. Las lineas de test se reencolan
|
||||
// El proximo reintento es en 1 dia
|
||||
const proximoReintento = new Date()
|
||||
proximoReintento.setDate(new Date().getDate() + 1)
|
||||
|
||||
// 5. Reintentos en 1 dia
|
||||
for (const linea of lineasTest) {
|
||||
const lineaId = peticionesRevisar.data
|
||||
.find(e => e.iccid == linea.identifier.iccid)?.id
|
||||
|
||||
if (lineaId == undefined) continue; // Esto puede ser un problema si se generaliza
|
||||
|
||||
this.pauseRepo.updateTask({
|
||||
id: lineaId,
|
||||
next_check: proximoReintento
|
||||
})
|
||||
}
|
||||
|
||||
// 6. Operaciones de pausa/cancelacion definitiva
|
||||
for (const linea of lineasNoTest) {
|
||||
const operacion = peticionesRevisar.data
|
||||
.find(e => e.iccid == linea.identifier.iccid)
|
||||
|
||||
if (operacion == undefined) continue;
|
||||
const dueDate = new Date()
|
||||
dueDate.setMinutes(new Date().getMinutes() + 15)
|
||||
|
||||
const operacionTipo = operacion.operation_type
|
||||
const actionData = operacion.action_data
|
||||
const correlation_id = operacion.action_data.correlation_id
|
||||
actionData.dueDate = dueDate.toISOString()
|
||||
|
||||
switch (linea.status.billingStatus) {
|
||||
case "ACTIVATED":
|
||||
let exito = false;
|
||||
let result = null;
|
||||
// IMPORTANTE COMRPOBAR EL DUE DATE
|
||||
switch (operacionTipo) {
|
||||
case "suspend":
|
||||
result = await this.simUsecases.suspend(actionData)()
|
||||
break;
|
||||
case "terminate":
|
||||
result = await this.simUsecases.terminate(actionData)()
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == undefined) {
|
||||
logger.error("Operacion desconocida", operacion)
|
||||
} else if (result?.error != undefined) {
|
||||
// error usecase
|
||||
logger.error(result.error)
|
||||
await this.pauseRepo.finishTask({
|
||||
id: operacion.id,
|
||||
error: result.error
|
||||
})
|
||||
if (correlation_id != undefined)
|
||||
await this.orderRepo.errorOrder({
|
||||
correlation_id: correlation_id,
|
||||
status: "dlx",
|
||||
reason: result.error
|
||||
})
|
||||
} else {
|
||||
// ok
|
||||
await this.pauseRepo.finishTask({ id: operacion.id })
|
||||
if (correlation_id != undefined)
|
||||
await this.orderRepo.finishOrder({ correlation_id })
|
||||
}
|
||||
|
||||
break;
|
||||
case "CANCELED":
|
||||
await this.pauseRepo.finishTask({
|
||||
id: operacion.id,
|
||||
error: "billingStatus is CANCELED"
|
||||
})
|
||||
if (correlation_id != undefined)
|
||||
await this.orderRepo.finishOrder({ correlation_id })
|
||||
break;
|
||||
case "SUSPENDED":
|
||||
await this.pauseRepo.finishTask({
|
||||
id: operacion.id,
|
||||
error: "billingStatus is SUSPENDED"
|
||||
})
|
||||
if (correlation_id != undefined)
|
||||
await this.orderRepo.finishOrder({ correlation_id })
|
||||
break;
|
||||
case "TEST":
|
||||
// No puede ser
|
||||
default:
|
||||
logger.error("billingStatus desconocido", linea.status.billingStatus)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
finExito()
|
||||
} catch (e) {
|
||||
finError(e)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -1,94 +1,14 @@
|
||||
import assert from "node:assert";
|
||||
import { lineToCreateLineDto, ObjeniousLine, ObjeniousLineResponse } from "sim-shared/domain/objeniousLine.js";
|
||||
import { tryCatch, Result } from "sim-shared/domain/Result.js";
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
import { lineToCreateLineDto, ObjeniousLine } from "sim-shared/domain/objeniousLine.js";
|
||||
import { ObjeniousLinesRepository } from "../infranstructure/ObjeniousLinesRepository.js";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { constants } from "node:buffer";
|
||||
|
||||
const MAX_PAGE_SIZE = 100
|
||||
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js";
|
||||
|
||||
export class TaskVolcadoLineas {
|
||||
constructor(
|
||||
private readonly httpClient: HttpClient,
|
||||
private readonly linesRepository: ObjeniousLinesRepository,
|
||||
private readonly objeniousRepository: ObjeniousOperationsRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mover al repo
|
||||
*/
|
||||
private async * getLinesByStatus(args?: {
|
||||
pageSize?: number,
|
||||
pageNumber?: number,
|
||||
status?: string
|
||||
}): AsyncGenerator<Result<string, ObjeniousLine[]>, Result<string, ObjeniousLine[]>, any> {
|
||||
|
||||
const path = "/lines"
|
||||
const pageSize = args?.pageSize ?? MAX_PAGE_SIZE;
|
||||
|
||||
let currentPage = args?.pageNumber ?? 0;
|
||||
let totalPages: number | undefined = undefined; // Como limite de paginas, igual es pasarse pero hasta que se lea
|
||||
|
||||
const params: Record<string, string | number> = {}
|
||||
|
||||
const loadNextLine = async (page: number): Promise<Result<string, ObjeniousLine[]>> => {
|
||||
if (args?.status != undefined) params["simStatus"] = args.status
|
||||
params["pageSize"] = pageSize
|
||||
params["pageNumber"] = page
|
||||
console.log("Params", params)
|
||||
console.log(`[i] Cargando pagina ${currentPage} de ${totalPages ?? "(desc)"}`)
|
||||
const nextPage = await tryCatch<AxiosResponse<ObjeniousLineResponse>>(this.httpClient.client.get(path, {
|
||||
params: params
|
||||
}))
|
||||
|
||||
if (nextPage.error != undefined) {
|
||||
console.error(nextPage.error.msg)
|
||||
return {
|
||||
error: nextPage.error.msg.message
|
||||
}
|
||||
}
|
||||
|
||||
// Se aumenta para la siguiente ejecucion
|
||||
console.log(`[i] Página ${currentPage} completa, total: ${nextPage.data.data.totalPages}`)
|
||||
totalPages = nextPage.data.data.totalPages
|
||||
|
||||
return {
|
||||
data: nextPage.data.data.content
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// El inicio se ejecuta siempre
|
||||
const lines = await loadNextLine(currentPage)
|
||||
|
||||
if (lines.error != undefined) {
|
||||
console.error("[x] Error obteniendo las lineas, cancelando operación");
|
||||
return {
|
||||
error: "Error cargando lineas"
|
||||
}
|
||||
}
|
||||
|
||||
currentPage++;
|
||||
|
||||
yield {
|
||||
data: lines.data
|
||||
}
|
||||
|
||||
// Copia para evitar bucles infinitos por error de la api
|
||||
const maxPages = totalPages
|
||||
assert.ok(maxPages != undefined, "No se ha defindo el numero de paginas") // Nunca deberia pasar pero así se evitan bucles infnitos
|
||||
console.log("maxPages", maxPages)
|
||||
for (let i = currentPage; i < maxPages!; i++) {
|
||||
console.log("Bucle i:", i, "page: ", currentPage)
|
||||
yield await loadNextLine(currentPage);
|
||||
currentPage++;
|
||||
}
|
||||
|
||||
return {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
private async saveLines(lines: ObjeniousLine[]) {
|
||||
const linesToCreate = lines.map(lineToCreateLineDto)
|
||||
@@ -107,7 +27,9 @@ export class TaskVolcadoLineas {
|
||||
console.log("[i] Iniciando task de volcado de lineas de Objenious")
|
||||
// Carga todas las lineas en memoria, hay que comprobar que no se gaste demasiada
|
||||
|
||||
const linesIterator = this.getLinesByStatus()
|
||||
const linesIterator = this.objeniousRepository.getLinesByStatusAPI({
|
||||
pageSize: 100
|
||||
})
|
||||
let lines = await linesIterator.next()
|
||||
|
||||
if (lines.value.error != undefined || lines.value.data == undefined) {
|
||||
@@ -118,7 +40,6 @@ export class TaskVolcadoLineas {
|
||||
await this.saveLines(lines.value.data)
|
||||
|
||||
while (!lines.done) {
|
||||
console.log()
|
||||
lines = await linesIterator.next()
|
||||
if (lines.value.error != undefined || lines.value.data == undefined) {
|
||||
console.error("[x] Error cargando las lineas a volcar", lines.value.error)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { test, describe } from "vitest"
|
||||
import { JWTService } from "./JWT.service.js"
|
||||
import { jwtService } from "../config/jwtService.config.js"
|
||||
|
||||
describe("Tokens Objenious", () => {
|
||||
const jwtService = new JWTService()
|
||||
const jwt = jwtService
|
||||
|
||||
test("Solicicitud normal de auth", async () => {
|
||||
const token = await jwtService.getAccessToken()
|
||||
const token = await jwt.getAccessToken()
|
||||
console.log("acceso objenious", token)
|
||||
}),
|
||||
|
||||
test("Solicicitud de refresh de auth", async () => {
|
||||
const token = await jwtService.tryRefreshToken()
|
||||
const token = await jwt.tryRefreshToken()
|
||||
console.log("acceso refresh objenious", token)
|
||||
})
|
||||
})
|
||||
@@ -4,24 +4,24 @@
|
||||
* el cliente HTTP
|
||||
*/
|
||||
|
||||
import { env } from "#config/env/index.js";
|
||||
import fs from "fs"
|
||||
|
||||
import {
|
||||
JWTToken,
|
||||
JWTHeader,
|
||||
IJWTService
|
||||
IJWTService,
|
||||
JWTPayload
|
||||
} from "sim-shared/domain/JWT.js"
|
||||
import axios, { AxiosError } from "axios";
|
||||
|
||||
type GrantAccessRequestBody = {
|
||||
export type GrantAccessRequestBody = {
|
||||
grant_type: string,
|
||||
client_id: string,
|
||||
client_assertion_type: string,
|
||||
client_assertion: string
|
||||
}
|
||||
|
||||
type TokensRequestResponse = {
|
||||
export type TokensRequestResponse = {
|
||||
"access_token": string,
|
||||
"expires_in": number,
|
||||
"refresh_token": string
|
||||
@@ -32,41 +32,6 @@ type TokensRequestResponse = {
|
||||
"scope": string
|
||||
}
|
||||
|
||||
|
||||
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
||||
|
||||
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
||||
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
||||
|
||||
const DEFAULT_BODY: GrantAccessRequestBody = {
|
||||
grant_type: "client_credentials",
|
||||
client_id: env.OBJ_CLIENT_ID,
|
||||
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
client_assertion: env.OBJ_CLI_ASSERTION
|
||||
}
|
||||
|
||||
const REFRESH_BODY = {
|
||||
...DEFAULT_BODY,
|
||||
grant_type: "refresh_token",
|
||||
}
|
||||
|
||||
const DEFAULT_HEADERS = {
|
||||
"content-type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
function addIATHeaders(authHeaders: Object) {
|
||||
const headers = <JWTHeader>{
|
||||
...authHeaders,
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: GET_TOKEN_URL,
|
||||
jti: Date.now().toString(),
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
export type ObjeniousTokenBody = any
|
||||
|
||||
/**
|
||||
@@ -82,27 +47,54 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
|
||||
public authToken: JWTToken<ObjeniousTokenBody> | undefined;
|
||||
private refreshToken?: JWTToken<ObjeniousTokenBody> | undefined;
|
||||
|
||||
constructor(args?: {
|
||||
// http
|
||||
private transformHeaders?: (_: Object) => JWTHeader;
|
||||
private defaultHttpHeaders: Record<string, string>;
|
||||
private defaultBody: Record<string, string>;
|
||||
|
||||
// jwt
|
||||
private defaultJWTHeaders: JWTHeader;
|
||||
private defaultJWTPayload: JWTPayload<any>;
|
||||
private privateKeyPath: string;
|
||||
private tokenUrl: string;
|
||||
private refreshTokenUrl: string;
|
||||
|
||||
|
||||
constructor(args: {
|
||||
token?: string // si se partiese de un token existente,
|
||||
refreshToken?: string
|
||||
refreshToken?: string,
|
||||
transformJWTHeaders?: (_: Object) => JWTHeader,
|
||||
defaultHeaders: Record<string, string>,
|
||||
defaultBody: Record<string, string>,
|
||||
defaultJWTHeaders: JWTHeader,
|
||||
defaultJWTPayload: JWTPayload<any>,
|
||||
privateKeyPath: string,
|
||||
tokenUrl: string,
|
||||
refreshTokenUrl: string
|
||||
}) {
|
||||
if (args?.token != undefined) this.authToken = new JWTToken(args.token)
|
||||
if (args?.refreshToken != undefined) this.refreshToken = new JWTToken(args.refreshToken)
|
||||
if (args?.transformJWTHeaders != undefined) this.transformHeaders = args.transformJWTHeaders
|
||||
|
||||
this.defaultHttpHeaders = args.defaultHeaders;
|
||||
this.defaultBody = args.defaultBody;
|
||||
|
||||
this.defaultJWTHeaders = args.defaultJWTHeaders;
|
||||
this.defaultJWTPayload = args.defaultJWTPayload;
|
||||
this.privateKeyPath = args.privateKeyPath;
|
||||
|
||||
this.tokenUrl = args.tokenUrl;
|
||||
this.refreshTokenUrl = args.refreshTokenUrl;
|
||||
}
|
||||
|
||||
private buildJwtBody() {
|
||||
const jwtHeaders = {
|
||||
alg: "RS256",
|
||||
typ: "JWT",
|
||||
kid: env.OBJ_KID
|
||||
}
|
||||
const jwtData = addIATHeaders({
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||
jti: Date.now().toString(),
|
||||
})
|
||||
const key = fs.readFileSync(PRIVATE_KEY_PATH, "utf8")
|
||||
const jwtHeaders = this.defaultJWTHeaders
|
||||
|
||||
const jwtData = (this.transformHeaders) ?
|
||||
this.transformHeaders(this.defaultJWTPayload) :
|
||||
this.defaultJWTPayload;
|
||||
|
||||
const key = fs.readFileSync(this.privateKeyPath, "utf8")
|
||||
const token = JWTToken.fromParts({
|
||||
header: jwtHeaders,
|
||||
payload: jwtData,
|
||||
@@ -116,14 +108,16 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
|
||||
|
||||
public async getNewAuthToken() {
|
||||
const bodyWithtoken = {
|
||||
...DEFAULT_BODY,
|
||||
...this.defaultBody,
|
||||
client_assertion: this.buildJwtBody()
|
||||
}
|
||||
|
||||
const req = axios.post(GET_TOKEN_URL,
|
||||
const headers = (this.transformHeaders) ? this.transformHeaders(this.defaultHttpHeaders) : this.defaultHttpHeaders;
|
||||
|
||||
const req = axios.post(this.tokenUrl,
|
||||
bodyWithtoken,
|
||||
{
|
||||
headers: addIATHeaders(DEFAULT_HEADERS)
|
||||
headers: headers
|
||||
}
|
||||
)
|
||||
|
||||
@@ -166,16 +160,21 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
|
||||
if (this.refreshToken == undefined) throw new Error("El refreshToken no está definido")
|
||||
if (this.refreshToken.isExpired()) throw new Error("El refreshToken ha expirado")
|
||||
|
||||
const refreshBody = {
|
||||
...this.defaultBody,
|
||||
grant_type: "refresh_token",
|
||||
}
|
||||
|
||||
const body = {
|
||||
...REFRESH_BODY,
|
||||
...refreshBody,
|
||||
client_assertion: this.buildJwtBody(),
|
||||
refresh_token: this.refreshToken.rawToken
|
||||
}
|
||||
|
||||
const req = axios.post(REFRESH_TOKEN_URL,
|
||||
const req = axios.post(this.refreshTokenUrl,
|
||||
body,
|
||||
{
|
||||
headers: DEFAULT_HEADERS
|
||||
headers: this.defaultHttpHeaders
|
||||
}
|
||||
)
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
import { env, loadEnvFile } from "node:process";
|
||||
import { Pool } from "pg";
|
||||
import { PgClient } from "../infrastructure/PgClient.js";
|
||||
import { HttpClient } from "../infrastructure/HTTPClient.js";
|
||||
import { jwtService } from "./jwtService.config.js";
|
||||
|
||||
console.warn("[i!] Se está corriendo codigo de test")
|
||||
loadEnvFile("../../.env") // Global
|
||||
loadEnvFile("./test.env") // Local
|
||||
|
||||
// se hace una por servicio.
|
||||
export const pgPool = new Pool({
|
||||
@@ -24,4 +27,14 @@ export const postgresClient = new PgClient({
|
||||
pool: pgPool
|
||||
})
|
||||
|
||||
const OBJ_BASE_URL = "https://api-getway.objenious.com/ws"
|
||||
export const httpObjClient = new HttpClient({
|
||||
baseURL: OBJ_BASE_URL,
|
||||
headers: {
|
||||
"content-type": " application/json; charset=utf-8"
|
||||
},
|
||||
jwtManager: jwtService
|
||||
})
|
||||
|
||||
|
||||
console.warn(`[T] TEST DB : ${env.POSTGRES_DATABASE}@${env.POSTGRES_HOST}`)
|
||||
|
||||
67
packages/sim-shared/config/jwtService.config.ts
Normal file
67
packages/sim-shared/config/jwtService.config.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import assert from "assert"
|
||||
import { env, loadEnvFile } from "process"
|
||||
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
|
||||
import { JWTHeader } from "sim-shared/domain/JWT.js"
|
||||
|
||||
loadEnvFile("../../.env") // Global
|
||||
loadEnvFile("./test.env") // Local
|
||||
|
||||
assert(env.OBJ_CLIENT_ID != undefined)
|
||||
assert(env.OBJ_CLI_ASSERTION != undefined)
|
||||
assert(env.OBJ_PEM_PATH != undefined)
|
||||
|
||||
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
||||
|
||||
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
||||
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
||||
|
||||
|
||||
const DEFAULT_BODY: GrantAccessRequestBody = {
|
||||
grant_type: "client_credentials",
|
||||
client_id: env.OBJ_CLIENT_ID,
|
||||
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
client_assertion: env.OBJ_CLI_ASSERTION
|
||||
}
|
||||
|
||||
|
||||
const DEFAULT_HEADERS = {
|
||||
"content-type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
const DEFAULT_HEADERS_JWT = {
|
||||
alg: "RS256",
|
||||
typ: "JWT",
|
||||
kid: env.OBJ_KID,
|
||||
}
|
||||
|
||||
const DEFAULT_DATA_JWT = {
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||
jti: Date.now().toString(),
|
||||
|
||||
}
|
||||
|
||||
function addIATHeaders(authHeaders: Object) {
|
||||
const headers = <JWTHeader>{
|
||||
...authHeaders,
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: GET_TOKEN_URL,
|
||||
jti: Date.now().toString(),
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
export const jwtService = new JWTService({
|
||||
transformJWTHeaders: addIATHeaders,
|
||||
defaultHeaders: DEFAULT_HEADERS,
|
||||
defaultBody: DEFAULT_BODY,
|
||||
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
|
||||
defaultJWTPayload: DEFAULT_DATA_JWT,
|
||||
privateKeyPath: PRIVATE_KEY_PATH,
|
||||
tokenUrl: GET_TOKEN_URL,
|
||||
refreshTokenUrl: REFRESH_TOKEN_URL
|
||||
})
|
||||
@@ -14,7 +14,7 @@ export type Failure<E = Error> = {
|
||||
*/
|
||||
export type Result<E, D> = Failure<E> | Success<D>
|
||||
|
||||
export async function tryCatch<T>(func: Promise<T>): Promise<Result<{ msg: Error }, T>> {
|
||||
export async function tryCatch<T>(func: Promise<T>): Promise<Result<Error, T>> {
|
||||
try {
|
||||
const res = await func;
|
||||
return {
|
||||
@@ -22,9 +22,8 @@ export async function tryCatch<T>(func: Promise<T>): Promise<Result<{ msg: Error
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
return {
|
||||
error: {
|
||||
msg: e as Error
|
||||
}
|
||||
error: e as Error
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ export type ObjeniousLine = {
|
||||
commercialStatus: string, //"test",
|
||||
commercialStatusDate: string, //"2026-03-17T11:41:01.493+00:00",
|
||||
networkStatus: string, // "ACTIVATED",
|
||||
billingStatus: string, //"TEST",
|
||||
billingStatus: "ACTIVATED" | "SUSPENDED" | "CANCELED" | "TEST",
|
||||
billingStatusChangeDate: string | null, // "2026-03-17T11:01:00.276+00:00",
|
||||
billingActivationDate: string | null //,
|
||||
createdDate: string | null,//"2026-01-30T01:50:02.060+00:00"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { describe, it } from "node:test";
|
||||
import { ObjeniousOperationsRepository } from "./ObjeniousOperationRepository.js";
|
||||
import { httpObjClient, postgresClient } from "../config/config.test.js";
|
||||
|
||||
describe("[Integration] Test API requests", () => {
|
||||
const repository = new ObjeniousOperationsRepository(
|
||||
httpObjClient,
|
||||
postgresClient
|
||||
)
|
||||
|
||||
it("Read /lines with multiple iccids", () => {
|
||||
|
||||
})
|
||||
})
|
||||
@@ -1,14 +1,139 @@
|
||||
import { IOperationsRepository, ObjeniousOperation, ObjeniousOperationChange } from "sim-shared/domain/operationsRepository.port.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { Result, tryCatch } from "sim-shared/domain/Result.js";
|
||||
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
|
||||
import { ObjeniousLine, ObjeniousLineResponse } from "../domain/objeniousLine.js";
|
||||
import { HttpClient } from "./HTTPClient.js";
|
||||
import assert from "node:assert";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
export class ObjeniousOperationsRepository implements IOperationsRepository {
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private readonly pgClient: PgClient
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Consulta el estado de una o mas lineas directamente a la API de Objenious
|
||||
* TODO: No hay paginacion como en getLinesByStatusAPI
|
||||
*/
|
||||
public async getLinesAPI(
|
||||
identifierType: "ICCID" | "IMSI" | "IMEI" | "MSISDN" | "REFERENCE",
|
||||
identifiers: string[]
|
||||
): Promise<Result<string, ObjeniousLine[]>> {
|
||||
if (identifiers.length == 0) {
|
||||
return {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
// Comprobar < MAX_PAGE_SIZE (Poco probable)
|
||||
|
||||
const path = "/lines"
|
||||
const params = {
|
||||
"identifier.identifierType": identifierType,
|
||||
"identifier.identifiers": identifiers.toString()
|
||||
}
|
||||
|
||||
const req = this.http.client.get<ObjeniousLineResponse>(path, {
|
||||
params: params
|
||||
})
|
||||
|
||||
const res = await tryCatch(req)
|
||||
|
||||
if (res.error != undefined) {
|
||||
return {
|
||||
error: res.error?.message
|
||||
}
|
||||
}
|
||||
|
||||
const lines = res.data.data.content
|
||||
|
||||
return {
|
||||
data: lines
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private MAX_PAGE_SIZE = 1000
|
||||
public async * getLinesByStatusAPI(args?: {
|
||||
pageSize?: number,
|
||||
pageNumber?: number,
|
||||
status?: string,
|
||||
iccids?: string[]
|
||||
}): AsyncGenerator<Result<string, ObjeniousLine[]>, Result<string, ObjeniousLine[]>, any> {
|
||||
|
||||
const path = "/lines"
|
||||
const pageSize = args?.pageSize ?? this.MAX_PAGE_SIZE;
|
||||
|
||||
let currentPage = args?.pageNumber ?? 0;
|
||||
let totalPages: number | undefined = undefined; // Como limite de paginas, igual es pasarse pero hasta que se lea
|
||||
|
||||
const params: Record<string, string | number> = {}
|
||||
|
||||
// Si se va a filtrar por iccids especificamente, en un futuro habra que ampliar el tipo de filtros
|
||||
if (args?.iccids != undefined) {
|
||||
params["identifier.identifierType"] = "ICCID"
|
||||
params["identifier.identifiers"] = args.iccids.toString()
|
||||
}
|
||||
|
||||
const loadNextLine = async (page: number): Promise<Result<string, ObjeniousLine[]>> => {
|
||||
if (args?.status != undefined) params["simStatus"] = args.status
|
||||
params["pageSize"] = pageSize
|
||||
params["pageNumber"] = page
|
||||
console.log(`[i] Cargando pagina ${currentPage} de ${totalPages ?? "(desc)"}`)
|
||||
const nextPage = await tryCatch<AxiosResponse<ObjeniousLineResponse>>(this.http.client.get(path, {
|
||||
params: params
|
||||
}))
|
||||
|
||||
if (nextPage.error != undefined) {
|
||||
console.error(nextPage.error)
|
||||
return {
|
||||
error: nextPage.error.message
|
||||
}
|
||||
}
|
||||
|
||||
// Se aumenta para la siguiente ejecucion
|
||||
console.log(`[i] Página ${currentPage} completa, total: ${nextPage.data.data.totalPages}`)
|
||||
totalPages = nextPage.data.data.totalPages
|
||||
|
||||
return {
|
||||
data: nextPage.data.data.content
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// El inicio se ejecuta siempre
|
||||
const lines = await loadNextLine(currentPage)
|
||||
|
||||
if (lines.error != undefined) {
|
||||
console.error("[x] Error obteniendo las lineas, cancelando operación");
|
||||
return {
|
||||
error: "Error cargando lineas"
|
||||
}
|
||||
}
|
||||
|
||||
currentPage++;
|
||||
|
||||
yield {
|
||||
data: lines.data
|
||||
}
|
||||
|
||||
// Copia para evitar bucles infinitos por error de la api
|
||||
const maxPages = totalPages
|
||||
assert.ok(maxPages != undefined, "No se ha defindo el numero de paginas") // Nunca deberia pasar pero así se evitan bucles infnitos
|
||||
console.log("maxPages", maxPages)
|
||||
for (let i = currentPage; i < maxPages!; i++) {
|
||||
console.log("Bucle i:", i, "page: ", currentPage)
|
||||
yield await loadNextLine(currentPage);
|
||||
currentPage++;
|
||||
}
|
||||
|
||||
return {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
async createOperation(data: ObjeniousOperation): Promise<Result<string, ObjeniousOperation>> {
|
||||
const query = `
|
||||
INSERT INTO objenious_operation (operation, iccids, status, max_retry, request_id)
|
||||
@@ -46,7 +171,7 @@ export class ObjeniousOperationsRepository implements IOperationsRepository {
|
||||
request_id = COALESCE($4, request_id),
|
||||
mass_action_id = COALESCE($5, mass_action_id),
|
||||
last_change_date = now() at time zone 'utc',
|
||||
end_date = CASE WHEN $2 IN ('finished') THEN now() at time zone 'utc' ELSE end_date END,
|
||||
end_date = CASE WHEN $2 IN ('finished','error') THEN now() at time zone 'utc' ELSE end_date END,
|
||||
objenious_status = $6
|
||||
WHERE id = $1`;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ describe("Test OrderRepository", {}, (ctx) => {
|
||||
before(async () => {
|
||||
// Order1
|
||||
const result1 = await orderRepo.createOrder(order1)
|
||||
assert(result1.data != undefined)
|
||||
assert.ok(result1.data != undefined, result1.error as string)
|
||||
testIds.push(result1.data.id)
|
||||
|
||||
// Order2 -> Para el test de crearOrder
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
*/
|
||||
import { PoolClient, QueryResult, QueryResultRow } from "pg";
|
||||
import { CreateOrderDTO, ErrorOrderDTO, FinishOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js";
|
||||
import { Result } from "../domain/Result.js";
|
||||
import { Result, tryCatch } from "../domain/Result.js";
|
||||
import { PgClient } from "./PgClient.js";
|
||||
import assert from "node:assert";
|
||||
import { error } from "node:console";
|
||||
|
||||
/**
|
||||
* Agrupa todas las operaciones de *Order*.
|
||||
@@ -19,9 +18,8 @@ import { error } from "node:console";
|
||||
*/
|
||||
export class OrderRepository {
|
||||
constructor(
|
||||
private readonly pgClient: PgClient
|
||||
private readonly pgClient: PgClient,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +55,8 @@ export class OrderRepository {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* El tipo <T> representa el contenido del mensaje de los order
|
||||
*/
|
||||
@@ -191,6 +191,8 @@ export class OrderRepository {
|
||||
const orderId = currentOrderResult.data?.id
|
||||
|
||||
if (orderId == undefined) {
|
||||
await client.query("ROLLBACK")
|
||||
client.release()
|
||||
return {
|
||||
error: "El order a actualizar no existe " + idType + ": " + idValue
|
||||
}
|
||||
@@ -261,7 +263,6 @@ export class OrderRepository {
|
||||
return updatedOrder
|
||||
}
|
||||
|
||||
|
||||
public async finishOrder(args: FinishOrderDTO) {
|
||||
const client = await this.pgClient.connect();
|
||||
assert((args.id != undefined) != (args.correlation_id != undefined))
|
||||
@@ -281,6 +282,8 @@ export class OrderRepository {
|
||||
const orderId = currentOrderResult.data?.id
|
||||
|
||||
if (orderId == undefined) {
|
||||
await client.query("ROLLBACK")
|
||||
client.release()
|
||||
return {
|
||||
error: "El order a actualizar no existe " + idType + ": " + idValue
|
||||
}
|
||||
|
||||
14
packages/sim-shared/test.env
Normal file
14
packages/sim-shared/test.env
Normal file
@@ -0,0 +1,14 @@
|
||||
## ENV PARA DATOS DE TEST - shared nunca se lanza en produccion
|
||||
|
||||
# claves de Objenious
|
||||
OBJ_PEM_PATH=./obj.pem
|
||||
OBJ_AUTHORIZATION=XOc7FtwXD8hUX2SFVX94XSty8wkOmChkwDNF09O_aIxPubMDdFUdCDCB4zpzSIxi8nOcTg7r_LM_nmd5qm7uLbksf_XArjI8iAyhjKz_2BAXPhmvKs4Fc9f3vv5LDfCVrPB9lP8P7rJ66_qnWs4jvhLQxSfn29m96hgXeCf8oySdIDUjN2q9Js3KAS5LL52Ri6ryvUeO1PvMhaPQMWRqoHIqTV1wPfPtiqQwcjUPmu5GeW164Kq1JLgV3KaGzfCZ9Qv9lbv30EJrukXxWuLCAhBS0kzrBXZoWvf2pb9uh3Am_93_dDxiIGQfIap9ZU_m8ZD1HPgvZOMCY6ZkxQconQ
|
||||
OBJ_CLI_ASSERTION=XOc7FtwXD8hUX2SFVX94XSty8wkOmChkwDNF09O_aIxPubMDdFUdCDCB4zpzSIxi8nOcTg7r_LM_nmd5qm7uLbksf_XArjI8iAyhjKz_2BAXPhmvKs4Fc9f3vv5LDfCVrPB9lP8P7rJ66_qnWs4jvhLQxSfn29m96hgXeCf8oySdIDUjN2q9Js3KAS5LL52Ri6ryvUeO1PvMhaPQMWRqoHIqTV1wPfPtiqQwcjUPmu5GeW164Kq1JLgV3KaGzfCZ9Qv9lbv30EJrukXxWuLCAhBS0kzrBXZoWvf2pb9uh3Am_93_dDxiIGQfIap9ZU_m8ZD1HPgvZOMCY6ZkxQconQ
|
||||
OBJ_CLIENT_ID=savefamily_rest_ws
|
||||
OBJ_KID=xNfbMiyL1ORXGP8lElhcv8nVaG3EJKye4Lc1YoN3I1E
|
||||
OBJ_BASE_URL=https://api-getway.objenious.com/ws
|
||||
# OBJ_BASE_URL=https://api-getway.objenious.com/ws/test
|
||||
|
||||
NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail"
|
||||
# NOTIFICATION_URL="localhost"
|
||||
SIM_ACTIVATION_API_KEY=9e48c4ac-1ab0-4397-b3f3-6c239200dfe6
|
||||
Reference in New Issue
Block a user