From 7d8835926367a1f2e26480e0f519c4e8d418e229 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Tue, 7 Apr 2026 13:20:31 +0200 Subject: [PATCH 01/10] Refactor de jwt y base de la bdd de pausas-cancelaciones --- .../1.2.0_Cola-pausa-cancelacion.sql | 24 ++++ .../aplication/Sim.controller.ts | 4 +- .../config/httpClient.config.ts | 4 +- .../config/jwtService.config.ts | 59 +++++++++ .../config/httpClient.config.ts | 5 +- .../config/jwtService.config.ts | 59 +++++++++ packages/sim-objenious-cron/index.ts | 15 ++- .../tasks/volcado_lineas.ts | 90 +------------ .../aplication/JWT.service.test.ts | 8 +- .../aplication/JWT.service.ts | 117 +++++++++-------- packages/sim-shared/config/config.test.ts | 13 ++ .../sim-shared/config/jwtService.config.ts | 67 ++++++++++ packages/sim-shared/domain/Result.ts | 7 +- .../ObjeniousOperationRepository.test.ts | 14 +++ .../ObjeniousOperationRepository.ts | 119 +++++++++++++++++- .../infrastructure/OrderRepository.test.ts | 2 +- .../infrastructure/OrderRepository.ts | 8 +- packages/sim-shared/test.env | 14 +++ 18 files changed, 465 insertions(+), 164 deletions(-) create mode 100644 deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql create mode 100644 packages/sim-consumidor-objenious/config/jwtService.config.ts create mode 100644 packages/sim-objenious-cron/config/jwtService.config.ts rename packages/{sim-consumidor-objenious => sim-shared}/aplication/JWT.service.test.ts (61%) rename packages/{sim-consumidor-objenious => sim-shared}/aplication/JWT.service.ts (66%) create mode 100644 packages/sim-shared/config/jwtService.config.ts create mode 100644 packages/sim-shared/infrastructure/ObjeniousOperationRepository.test.ts create mode 100644 packages/sim-shared/test.env diff --git a/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql b/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql new file mode 100644 index 0000000..44bb3dd --- /dev/null +++ b/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql @@ -0,0 +1,24 @@ +/** +* Para la tarea WEBINT-328-Pausas-cacelaciones. +* Almacena las pausas/cancelaciones que no se han podido hacer porque la linea esta en +* "Test" +*/ + +CREATE TABLE IF NOT EXISTS pause_cancel_tasks ( + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + iccid TEXT NOT NULL, + + 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 +); + +-- 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; + + diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts index 7c60673..b56db69 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts @@ -3,7 +3,6 @@ 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"; /** * La clase usa generadores de funciones para mantener el contexto @@ -157,6 +156,9 @@ export class SimController { } } + /** + * Lo mismo que pause + */ public suspend() { return async (msg: ConsumeMessage) => { let msgData; diff --git a/packages/sim-consumidor-objenious/config/httpClient.config.ts b/packages/sim-consumidor-objenious/config/httpClient.config.ts index 6d37725..427b6e8 100644 --- a/packages/sim-consumidor-objenious/config/httpClient.config.ts +++ b/packages/sim-consumidor-objenious/config/httpClient.config.ts @@ -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 }) diff --git a/packages/sim-consumidor-objenious/config/jwtService.config.ts b/packages/sim-consumidor-objenious/config/jwtService.config.ts new file mode 100644 index 0000000..3d9bf78 --- /dev/null +++ b/packages/sim-consumidor-objenious/config/jwtService.config.ts @@ -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 = { + ...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 +}) diff --git a/packages/sim-objenious-cron/config/httpClient.config.ts b/packages/sim-objenious-cron/config/httpClient.config.ts index 89fe5d1..c526054 100644 --- a/packages/sim-objenious-cron/config/httpClient.config.ts +++ b/packages/sim-objenious-cron/config/httpClient.config.ts @@ -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 }) diff --git a/packages/sim-objenious-cron/config/jwtService.config.ts b/packages/sim-objenious-cron/config/jwtService.config.ts new file mode 100644 index 0000000..3d9bf78 --- /dev/null +++ b/packages/sim-objenious-cron/config/jwtService.config.ts @@ -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 = { + ...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 +}) diff --git a/packages/sim-objenious-cron/index.ts b/packages/sim-objenious-cron/index.ts index 84e8cd7..9153d39 100644 --- a/packages/sim-objenious-cron/index.ts +++ b/packages/sim-objenious-cron/index.ts @@ -21,7 +21,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 +34,15 @@ async function startCron() { httpClient, ) - const volcadoLineasTask = new TaskVolcadoLineas(httpClient, objeniousLineRepository) + const objeniosRepo = new ObjeniousOperationsRepository( + httpClient, + pgClient + ) + + const volcadoLineasTask = new TaskVolcadoLineas( + objeniousLineRepository, + objeniosRepo + ) const PERIODO_PETICIONES = 10 * 60 * 1000 const interval = setInterval(async () => { diff --git a/packages/sim-objenious-cron/tasks/volcado_lineas.ts b/packages/sim-objenious-cron/tasks/volcado_lineas.ts index e8cbd8a..bea139a 100644 --- a/packages/sim-objenious-cron/tasks/volcado_lineas.ts +++ b/packages/sim-objenious-cron/tasks/volcado_lineas.ts @@ -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 "packages/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, 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 = {} - - const loadNextLine = async (page: number): Promise> => { - 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>(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) { diff --git a/packages/sim-consumidor-objenious/aplication/JWT.service.test.ts b/packages/sim-shared/aplication/JWT.service.test.ts similarity index 61% rename from packages/sim-consumidor-objenious/aplication/JWT.service.test.ts rename to packages/sim-shared/aplication/JWT.service.test.ts index d7f2f43..1d37d7e 100644 --- a/packages/sim-consumidor-objenious/aplication/JWT.service.test.ts +++ b/packages/sim-shared/aplication/JWT.service.test.ts @@ -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) }) }) diff --git a/packages/sim-consumidor-objenious/aplication/JWT.service.ts b/packages/sim-shared/aplication/JWT.service.ts similarity index 66% rename from packages/sim-consumidor-objenious/aplication/JWT.service.ts rename to packages/sim-shared/aplication/JWT.service.ts index 038cfb8..9ef739e 100644 --- a/packages/sim-consumidor-objenious/aplication/JWT.service.ts +++ b/packages/sim-shared/aplication/JWT.service.ts @@ -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 = { - ...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 { public authToken: JWTToken | undefined; private refreshToken?: JWTToken | undefined; - constructor(args?: { + // http + private transformHeaders?: (_: Object) => JWTHeader; + private defaultHttpHeaders: Record; + private defaultBody: Record; + + // jwt + private defaultJWTHeaders: JWTHeader; + private defaultJWTPayload: JWTPayload; + 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, + defaultBody: Record, + defaultJWTHeaders: JWTHeader, + defaultJWTPayload: JWTPayload, + 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 { 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 { 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 } ) diff --git a/packages/sim-shared/config/config.test.ts b/packages/sim-shared/config/config.test.ts index 22fb083..b8e2c7c 100644 --- a/packages/sim-shared/config/config.test.ts +++ b/packages/sim-shared/config/config.test.ts @@ -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}`) diff --git a/packages/sim-shared/config/jwtService.config.ts b/packages/sim-shared/config/jwtService.config.ts new file mode 100644 index 0000000..c2500b8 --- /dev/null +++ b/packages/sim-shared/config/jwtService.config.ts @@ -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 = { + ...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 +}) diff --git a/packages/sim-shared/domain/Result.ts b/packages/sim-shared/domain/Result.ts index a8bda25..0381424 100644 --- a/packages/sim-shared/domain/Result.ts +++ b/packages/sim-shared/domain/Result.ts @@ -14,7 +14,7 @@ export type Failure = { */ export type Result = Failure | Success -export async function tryCatch(func: Promise): Promise> { +export async function tryCatch(func: Promise): Promise> { try { const res = await func; return { @@ -22,9 +22,8 @@ export async function tryCatch(func: Promise): Promise { + const repository = new ObjeniousOperationsRepository( + httpObjClient, + postgresClient + ) + + it("Read /lines with multiple iccids", () => { + + }) +}) diff --git a/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts b/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts index 134da3f..a48523a 100644 --- a/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts +++ b/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts @@ -1,14 +1,131 @@ 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 + */ + public async getLinesAPI( + identifierType: "ICCID" | "IMSI" | "IMEI" | "MSISDN" | "REFERENCE", + identifiers: string[] + ): Promise> { + 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(path, { + params: params + }) + + const res = await tryCatch(req) + + if (res.error != undefined) { + return { + error: res.error?.message + } + } + + const lines = res.data.data + + return { + data: lines + } + } + + + private MAX_PAGE_SIZE = 1000 + public async * getLinesByStatusAPI(args?: { + pageSize?: number, + pageNumber?: number, + status?: string + }): AsyncGenerator, Result, 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 = {} + + const loadNextLine = async (page: number): Promise> => { + 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>(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> { const query = ` INSERT INTO objenious_operation (operation, iccids, status, max_retry, request_id) diff --git a/packages/sim-shared/infrastructure/OrderRepository.test.ts b/packages/sim-shared/infrastructure/OrderRepository.test.ts index 8b33947..4636fe3 100644 --- a/packages/sim-shared/infrastructure/OrderRepository.test.ts +++ b/packages/sim-shared/infrastructure/OrderRepository.test.ts @@ -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 diff --git a/packages/sim-shared/infrastructure/OrderRepository.ts b/packages/sim-shared/infrastructure/OrderRepository.ts index 15d18f8..b2e965a 100644 --- a/packages/sim-shared/infrastructure/OrderRepository.ts +++ b/packages/sim-shared/infrastructure/OrderRepository.ts @@ -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 representa el contenido del mensaje de los order */ diff --git a/packages/sim-shared/test.env b/packages/sim-shared/test.env new file mode 100644 index 0000000..1e5983d --- /dev/null +++ b/packages/sim-shared/test.env @@ -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 -- 2.49.1 From 39567970202f5492ff976ae44c31bde1db54a3be Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Tue, 7 Apr 2026 15:40:19 +0200 Subject: [PATCH 02/10] Las operaciones basicas del repositorio de pause/cancel funcionan y tienen test --- .../PauseCancelTaskRepository.test.ts | 57 +++++++++ .../PauseCancelTaskRepository.ts | 121 ++++++++++++++++++ .../sim-consumidor-objenious/package.json | 2 +- 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts create mode 100644 packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts diff --git a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts new file mode 100644 index 0000000..0d0e403 --- /dev/null +++ b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts @@ -0,0 +1,57 @@ +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", + activation_date: new Date(), + next_check: new Date() +} + +describe("Test PauseCancelTaskRepository - DB", () => { + function clean() { + + } + + 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 mus 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 mus 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 mus be returned") + }) +}) diff --git a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts new file mode 100644 index 0000000..ced1702 --- /dev/null +++ b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts @@ -0,0 +1,121 @@ +import { Result } from "sim-shared/domain/Result.js"; +import { QueryResult } from "pg"; +import { PgClient } from "sim-shared/infrastructure/PgClient.js"; +import { AxiosError } from "axios"; + +export type PauseCancelTask = { + id: number; + iccid: string; + last_checked?: Date | null; + activation_date?: Date | null; + next_check?: Date | null; + completed_date?: Date | null; + error?: string | null; +} + +export type CreatePauseCancelTaskDTO = Pick +export type UpdatePauseCancelTaskDTO = Pick +export type FinishPauseCancelTaskDTO = Pick + +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> { + 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 = await this.pgClient.query(sql); + return { + data: res.rows + } + } catch (e) { + return { + error: (e as AxiosError).message + } + } + } + + public async addTask(task: CreatePauseCancelTaskDTO): Promise> { + + const sql = ` + INSERT INTO pause_cancel_tasks (iccid, activation_date, next_check, last_checked) + VALUES ($1, $2, $3, now()) + RETURNING *; + `; + try { + const values = [task.iccid, task.activation_date, task.next_check]; + const res: QueryResult = 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> { + + const sql = ` + UPDATE pause_cancel_tasks + SET last_checked = now(), next_check = $1 + WHERE id = $2 + RETURNING *; + `; + try { + const res = await this.pgClient.query(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 + } + + } + } + +} diff --git a/packages/sim-consumidor-objenious/package.json b/packages/sim-consumidor-objenious/package.json index 135c24e..5fc7f6e 100644 --- a/packages/sim-consumidor-objenious/package.json +++ b/packages/sim-consumidor-objenious/package.json @@ -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", -- 2.49.1 From e6ff54a15dbfd0e1f11a5877462bc877076e8d64 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Tue, 7 Apr 2026 17:43:17 +0200 Subject: [PATCH 03/10] Usecases --- .../1.2.0_Cola-pausa-cancelacion.sql | 4 +- .../aplication/Sim.controller.ts | 2 +- .../aplication/Sim.usecases.ts | 101 +++++++++++++++++- packages/sim-consumidor-objenious/index.ts | 11 +- .../PauseCancelTaskRepository.test.ts | 2 + .../PauseCancelTaskRepository.ts | 15 +-- 6 files changed, 121 insertions(+), 14 deletions(-) diff --git a/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql b/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql index 44bb3dd..63bdba7 100644 --- a/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql +++ b/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql @@ -4,10 +4,12 @@ * "Test" */ +CREATE TYPE SUSPENDTERMINATE AS ENUM ('suspend','terminate'); + 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 diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts index b56db69..d50f59a 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts @@ -178,7 +178,7 @@ export class SimController { dueDate: this.genDueDate(2 * 60).toISOString(), identifier: { identifierType: "ICCID", - identifiers: [iccid] + identifiers: [iccid] // Por algún motivo solo he puesto un iccd por identifier } })) diff --git a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts index f7c79a5..bf14e22 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts @@ -5,6 +5,7 @@ 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" // TODO: // - Pasar a un archivo de DTOs @@ -12,21 +13,24 @@ import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js" export class SimUseCases { private readonly httpClient: HttpClient - private readonly operationRepository: OperationsRepositoryPort + private readonly objeniousRepository: OperationsRepositoryPort private readonly orderRepository: OrderRepository + private readonly pauseRepository: PauseCancelTaskRepository constructor(args: { httpClient: HttpClient, operationRepository: OperationsRepositoryPort, - orderRepository: OrderRepository + 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 +77,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)) @@ -238,6 +244,93 @@ export class SimUseCases { }) } + /** + * Paso previo a la suspension para evitar errores cuando el billing es test + */ + public stage_suspend(suspendData: ActionData): () => Promise> { + return async (): Promise> => { + const correlation_id = suspendData.correlation_id + + const newTask: CreatePauseCancelTaskDTO = { + iccid: suspendData.identifier.identifiers[0], + activation_date: new Date(), // TODO: BUSCAR LA DE VERDAD + next_check: undefined, // Que se haga instantaneamente al ser la primera + operation_type: "terminate" + } + + 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 + } + } + } + + /** + * Paso previo a la suspension para evitar errores cuando el billing es test + */ + public stage_terminate(terminateData: ActionData): () => Promise> { + return async (): Promise> => { + const correlation_id = terminateData.correlation_id + + const newTask: CreatePauseCancelTaskDTO = { + iccid: terminateData.identifier.identifiers[0], + activation_date: new Date(), // TODO: BUSCAR LA DE VERDAD + next_check: undefined, // Que se haga instantaneamente al ser la primera + operation_type: "terminate" + } + + 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> { const OPERATION_URL = "/actions/terminateLine" return this.generateUseCase({ diff --git a/packages/sim-consumidor-objenious/index.ts b/packages/sim-consumidor-objenious/index.ts index c2d836c..01e9d17 100644 --- a/packages/sim-consumidor-objenious/index.ts +++ b/packages/sim-consumidor-objenious/index.ts @@ -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) diff --git a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts index 0d0e403..4facb2c 100644 --- a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts +++ b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts @@ -5,6 +5,7 @@ import assert from "node:assert"; const testTask: CreatePauseCancelTaskDTO = { iccid: "1234", + operation_type: "suspend", activation_date: new Date(), next_check: new Date() } @@ -21,6 +22,7 @@ describe("Test PauseCancelTaskRepository - DB", () => { before(() => { }) + after(() => { }) diff --git a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts index ced1702..b861c7d 100644 --- a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts +++ b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts @@ -6,6 +6,7 @@ import { AxiosError } from "axios"; export type PauseCancelTask = { id: number; iccid: string; + operation_type: "suspend" | "terminate", last_checked?: Date | null; activation_date?: Date | null; next_check?: Date | null; @@ -13,15 +14,18 @@ export type PauseCancelTask = { error?: string | null; } -export type CreatePauseCancelTaskDTO = Pick +export type CreatePauseCancelTaskDTO = Pick export type UpdatePauseCancelTaskDTO = Pick export type FinishPauseCancelTaskDTO = Pick +/** + * 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 ) { - } /** @@ -51,12 +55,12 @@ export class PauseCancelTaskRepository { public async addTask(task: CreatePauseCancelTaskDTO): Promise> { const sql = ` - INSERT INTO pause_cancel_tasks (iccid, activation_date, next_check, last_checked) - VALUES ($1, $2, $3, now()) + INSERT INTO pause_cancel_tasks (iccid, activation_date, next_check, last_checked, operation_type) + VALUES ($1, $2, $3, now(), $4) RETURNING *; `; try { - const values = [task.iccid, task.activation_date, task.next_check]; + const values = [task.iccid, task.activation_date, task.next_check, task.operation_type]; const res: QueryResult = await this.pgClient.query(sql, values); return { data: res.rows[0] @@ -66,7 +70,6 @@ export class PauseCancelTaskRepository { error: (e as AxiosError).message } } - } /** -- 2.49.1 From 4168949b9e7e2e88b587286eb35020dfe531c025 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Wed, 8 Apr 2026 10:08:54 +0200 Subject: [PATCH 04/10] Endpoint preparados --- .../aplication/Sim.controller.test.ts | 112 ++++++++++++++++++ .../aplication/Sim.controller.ts | 21 +++- .../aplication/Sim.usecases.ts | 3 +- 3 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts new file mode 100644 index 0000000..f2f3da9 --- /dev/null +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts @@ -0,0 +1,112 @@ +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 } 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"; + +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 + }); + + 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; + }; + + 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"); + }); + }); +}); diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts index d50f59a..ca103a0 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts @@ -3,6 +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 { ActionData } from "#domain/DTOs/objeniousapi.js"; /** * La clase usa generadores de funciones para mantener el contexto @@ -36,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; } @@ -173,14 +175,19 @@ 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] // 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)) + */ } } @@ -197,16 +204,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)) + } } diff --git a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts index bf14e22..0048a7a 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts @@ -95,7 +95,6 @@ export class SimUseCases { error: undefined, data: true } - } else { return { error: String(response.status), @@ -255,7 +254,7 @@ export class SimUseCases { iccid: suspendData.identifier.identifiers[0], activation_date: new Date(), // TODO: BUSCAR LA DE VERDAD next_check: undefined, // Que se haga instantaneamente al ser la primera - operation_type: "terminate" + operation_type: "suspend" } const taskCreated = await this.pauseRepository.addTask(newTask) -- 2.49.1 From a27e4b30d26a336cebc03b972a7a62782ac424c6 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Wed, 8 Apr 2026 13:48:57 +0200 Subject: [PATCH 05/10] Cron completo y mejora de logs --- .../1.2.0_Cola-pausa-cancelacion.sql | 10 +- .../aplication/Sim.controller.test.ts | 5 +- .../aplication/Sim.controller.ts | 1 + .../aplication/Sim.usecases.ts | 68 ++++++-- .../PauseCancelTaskRepository.test.ts | 26 ++- .../PauseCancelTaskRepository.ts | 10 +- .../tasks/check_pause_terminate.ts | 157 ++++++++++++++++++ .../tasks/volcado_lineas.ts | 3 +- packages/sim-shared/domain/objeniousLine.ts | 2 +- .../ObjeniousOperationRepository.ts | 10 +- 10 files changed, 261 insertions(+), 31 deletions(-) create mode 100644 packages/sim-objenious-cron/tasks/check_pause_terminate.ts diff --git a/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql b/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql index 63bdba7..9a39e62 100644 --- a/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql +++ b/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql @@ -4,7 +4,12 @@ * "Test" */ -CREATE TYPE SUSPENDTERMINATE AS ENUM ('suspend','terminate'); + +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, @@ -15,7 +20,8 @@ CREATE TABLE IF NOT EXISTS pause_cancel_tasks ( 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 + error TEXT, + actionData JSONB -- datos de la operacion original. ); -- Indice de las tareas que no han terminado diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts index f2f3da9..facacfc 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts @@ -9,6 +9,7 @@ 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; @@ -31,13 +32,14 @@ describe("SimController Integration Tests (Real UseCases)", () => { ); 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); }); @@ -70,6 +72,7 @@ describe("SimController Integration Tests (Real UseCases)", () => { const handler = controller.suspend(); await handler(msg); + console.log("Nack: ", eventBusMock.nack.mock.callCount()) // 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"); diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts index ca103a0..92e445c 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts @@ -185,6 +185,7 @@ export class SimController { } const useCaseRes = await this.tryUseCase(msg, this.useCases.stage_suspend(suspendData)) + console.log("res::", useCaseRes) /* const res = await this.tryUseCase(msg, this.useCases.suspend(actionData)) */ diff --git a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts index 0048a7a..355aa41 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts @@ -6,6 +6,7 @@ import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort } 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 @@ -13,13 +14,13 @@ import { CreatePauseCancelTaskDTO, PauseCancelTaskRepository } from "#adapters/P export class SimUseCases { private readonly httpClient: HttpClient - private readonly objeniousRepository: OperationsRepositoryPort + private readonly objeniousRepository: ObjeniousOperationsRepository private readonly orderRepository: OrderRepository private readonly pauseRepository: PauseCancelTaskRepository constructor(args: { httpClient: HttpClient, - operationRepository: OperationsRepositoryPort, + operationRepository: ObjeniousOperationsRepository, orderRepository: OrderRepository, pauseRepository: PauseCancelTaskRepository }) { @@ -243,31 +244,64 @@ 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 + 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> { return async (): Promise> => { const correlation_id = suspendData.correlation_id + const iccid = suspendData.identifier.identifiers - const newTask: CreatePauseCancelTaskDTO = { - iccid: suspendData.identifier.identifiers[0], - activation_date: new Date(), // TODO: BUSCAR LA DE VERDAD - next_check: undefined, // Que se haga instantaneamente al ser la primera - operation_type: "suspend" - } - - 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) + const fail = (error: string) => { + console.error("[Sim.usecases]", error) if (correlation_id != undefined) { this.orderRepository.updateOrder({ correlation_id: correlation_id, new_status: "failed" }) } + } + + console.log("Preactivationdate", suspendData) + const activationDate = await this.findActivationDate(suspendData) + console.log("ActivationDate", activationDate) + + const newTask: CreatePauseCancelTaskDTO = { + iccid: iccid[0], + activation_date: activationDate, + next_check: undefined, // Que se haga instantaneamente al ser la primera + operation_type: "suspend", + actionData: 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 } @@ -294,11 +328,13 @@ export class SimUseCases { return async (): Promise> => { const correlation_id = terminateData.correlation_id + const activationDate = await this.findActivationDate(terminateData) const newTask: CreatePauseCancelTaskDTO = { iccid: terminateData.identifier.identifiers[0], - activation_date: new Date(), // TODO: BUSCAR LA DE VERDAD + activation_date: activationDate, next_check: undefined, // Que se haga instantaneamente al ser la primera - operation_type: "terminate" + operation_type: "terminate", + actionData: terminateData } const taskCreated = await this.pauseRepository.addTask(newTask) diff --git a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts index 4facb2c..9640031 100644 --- a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts +++ b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts @@ -7,7 +7,15 @@ const testTask: CreatePauseCancelTaskDTO = { iccid: "1234", operation_type: "suspend", activation_date: new Date(), - next_check: new Date() + next_check: new Date(), + actionData: { + dueDate: new Date().toString(), + correlation_id: "12223", + identifier: { + identifiers: ["1234"], + identifierType: "ICCID" + } + } } describe("Test PauseCancelTaskRepository - DB", () => { @@ -31,7 +39,7 @@ describe("Test PauseCancelTaskRepository - DB", () => { 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 mus be returned") + assert.ok(created.data != undefined, "Data must be returned") createdIds.push(created.data.id) }) @@ -43,7 +51,7 @@ describe("Test PauseCancelTaskRepository - DB", () => { 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 mus be returned") + assert.ok(updated.data != undefined, "Data must be returned") }) it("Should finish a existing task", async () => { @@ -54,6 +62,16 @@ describe("Test PauseCancelTaskRepository - DB", () => { 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 mus be returned") + assert.ok(finish.data != undefined, "Data must be returned") + }) + + it("Should get at least 1 pending task", async () => { + 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) }) }) diff --git a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts index b861c7d..30ee561 100644 --- a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts +++ b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts @@ -2,6 +2,7 @@ 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; @@ -12,9 +13,10 @@ export type PauseCancelTask = { next_check?: Date | null; completed_date?: Date | null; error?: string | null; + actionData: ActionData } -export type CreatePauseCancelTaskDTO = Pick +export type CreatePauseCancelTaskDTO = Pick export type UpdatePauseCancelTaskDTO = Pick export type FinishPauseCancelTaskDTO = Pick @@ -55,12 +57,12 @@ export class PauseCancelTaskRepository { public async addTask(task: CreatePauseCancelTaskDTO): Promise> { const sql = ` - INSERT INTO pause_cancel_tasks (iccid, activation_date, next_check, last_checked, operation_type) - VALUES ($1, $2, $3, now(), $4) + 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]; + const values = [task.iccid, task.activation_date, task.next_check, task.operation_type, JSON.stringify(task.actionData)]; const res: QueryResult = await this.pgClient.query(sql, values); return { data: res.rows[0] diff --git a/packages/sim-objenious-cron/tasks/check_pause_terminate.ts b/packages/sim-objenious-cron/tasks/check_pause_terminate.ts new file mode 100644 index 0000000..7f85fa5 --- /dev/null +++ b/packages/sim-objenious-cron/tasks/check_pause_terminate.ts @@ -0,0 +1,157 @@ +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 TaskPauseTerminate { + 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) + } + 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`) + + // 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() + } + + // 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.actionData + const correlation_id = operacion.actionData.correlation_id + actionData.dueDate = dueDate.toString() + + if (linea.status.billingStatus == "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 }) + } + } + + // TODO: SUSPENDED Y TERMINATED + } + + + logger.log("Finalizado con exito proceso de comprobacion de lineas en pausa o canceladas") + } catch (e) { + finError(e) + } + + return 0 + } +} diff --git a/packages/sim-objenious-cron/tasks/volcado_lineas.ts b/packages/sim-objenious-cron/tasks/volcado_lineas.ts index bea139a..d06eb87 100644 --- a/packages/sim-objenious-cron/tasks/volcado_lineas.ts +++ b/packages/sim-objenious-cron/tasks/volcado_lineas.ts @@ -1,6 +1,6 @@ import { lineToCreateLineDto, ObjeniousLine } from "sim-shared/domain/objeniousLine.js"; import { ObjeniousLinesRepository } from "../infranstructure/ObjeniousLinesRepository.js"; -import { ObjeniousOperationsRepository } from "packages/sim-shared/infrastructure/ObjeniousOperationRepository.js"; +import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js"; export class TaskVolcadoLineas { constructor( @@ -40,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) diff --git a/packages/sim-shared/domain/objeniousLine.ts b/packages/sim-shared/domain/objeniousLine.ts index c04eb30..cf48f3b 100644 --- a/packages/sim-shared/domain/objeniousLine.ts +++ b/packages/sim-shared/domain/objeniousLine.ts @@ -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" diff --git a/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts b/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts index a48523a..68d0009 100644 --- a/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts +++ b/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts @@ -16,6 +16,7 @@ export class ObjeniousOperationsRepository implements IOperationsRepository { /** * 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", @@ -59,7 +60,8 @@ export class ObjeniousOperationsRepository implements IOperationsRepository { public async * getLinesByStatusAPI(args?: { pageSize?: number, pageNumber?: number, - status?: string + status?: string, + iccids?: string[] }): AsyncGenerator, Result, any> { const path = "/lines" @@ -70,6 +72,12 @@ export class ObjeniousOperationsRepository implements IOperationsRepository { const params: Record = {} + // 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> => { if (args?.status != undefined) params["simStatus"] = args.status params["pageSize"] = pageSize -- 2.49.1 From a9589f578b05686b2253733d2b61db45cc5ad713 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Wed, 8 Apr 2026 14:47:57 +0200 Subject: [PATCH 06/10] Solucionado cierrre de pool para test --- .../aplication/Sim.controller.test.ts | 7 +++++-- .../sim-consumidor-objenious/aplication/Sim.controller.ts | 1 - .../sim-consumidor-objenious/aplication/Sim.usecases.ts | 2 -- packages/sim-shared/infrastructure/OrderRepository.ts | 5 ++++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts index facacfc..b408723 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.test.ts @@ -4,7 +4,7 @@ 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 } from "#config/postgreConfig.js"; +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"; @@ -56,6 +56,10 @@ describe("SimController Integration Tests (Real UseCases)", () => { } 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(); @@ -72,7 +76,6 @@ describe("SimController Integration Tests (Real UseCases)", () => { const handler = controller.suspend(); await handler(msg); - console.log("Nack: ", eventBusMock.nack.mock.callCount()) // 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"); diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts index 92e445c..ca103a0 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts @@ -185,7 +185,6 @@ export class SimController { } const useCaseRes = await this.tryUseCase(msg, this.useCases.stage_suspend(suspendData)) - console.log("res::", useCaseRes) /* const res = await this.tryUseCase(msg, this.useCases.suspend(actionData)) */ diff --git a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts index 355aa41..ccb822c 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts @@ -285,9 +285,7 @@ export class SimUseCases { } } - console.log("Preactivationdate", suspendData) const activationDate = await this.findActivationDate(suspendData) - console.log("ActivationDate", activationDate) const newTask: CreatePauseCancelTaskDTO = { iccid: iccid[0], diff --git a/packages/sim-shared/infrastructure/OrderRepository.ts b/packages/sim-shared/infrastructure/OrderRepository.ts index b2e965a..757e9e8 100644 --- a/packages/sim-shared/infrastructure/OrderRepository.ts +++ b/packages/sim-shared/infrastructure/OrderRepository.ts @@ -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 } -- 2.49.1 From 7ff3f13af4f5934a4dd4761266125ebf9c95042d Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Wed, 8 Apr 2026 17:37:47 +0200 Subject: [PATCH 07/10] Funcionan las suspensiones --- docs/sim-api/Pause.bru | 2 +- docs/sim-api/environments/local.bru | 1 + docs/sim-api/environments/prod.bru | 1 + .../aplication/Sim.controller.ts | 2 +- .../aplication/Sim.usecases.ts | 12 +++++++-- .../aplication/Sim.controller.ts | 1 - packages/sim-objenious-cron/index.ts | 25 ++++++++++++++++++- .../tasks/check_pause_terminate.ts | 2 +- .../ObjeniousOperationRepository.ts | 4 +-- 9 files changed, 41 insertions(+), 9 deletions(-) diff --git a/docs/sim-api/Pause.bru b/docs/sim-api/Pause.bru index cef1ed7..bcb3b8e 100644 --- a/docs/sim-api/Pause.bru +++ b/docs/sim-api/Pause.bru @@ -15,7 +15,7 @@ params:query { } body:form-urlencoded { - iccid: 8933201125065160414 + iccid: 8933201125068886692 } settings { diff --git a/docs/sim-api/environments/local.bru b/docs/sim-api/environments/local.bru index 79faa0b..fab4907 100644 --- a/docs/sim-api/environments/local.bru +++ b/docs/sim-api/environments/local.bru @@ -1,3 +1,4 @@ vars { baseurl: http://localhost:3000 } +color: #2E8A54 diff --git a/docs/sim-api/environments/prod.bru b/docs/sim-api/environments/prod.bru index 1ef2e33..d6e2471 100644 --- a/docs/sim-api/environments/prod.bru +++ b/docs/sim-api/environments/prod.bru @@ -1,3 +1,4 @@ vars { baseurl: https://sf-sims.savefamilygps.net } +color: #CE4F3B diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts index ca103a0..800cf98 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts @@ -164,6 +164,7 @@ export class SimController { public suspend() { return async (msg: ConsumeMessage) => { let msgData; + console.log("Consumiendo susension", msg) try { msgData = this.validateMsg(msg) as SimEvents.pause } catch (e) { @@ -183,7 +184,6 @@ export class SimController { 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)) diff --git a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts index ccb822c..13c0b1a 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts @@ -255,6 +255,7 @@ export class SimUseCases { // 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 { @@ -272,6 +273,7 @@ export class SimUseCases { */ public stage_suspend(suspendData: ActionData): () => Promise> { return async (): Promise> => { + console.log("Suspend action data", suspendData) const correlation_id = suspendData.correlation_id const iccid = suspendData.identifier.identifiers @@ -285,8 +287,14 @@ export class SimUseCases { } } - const activationDate = await this.findActivationDate(suspendData) - + let activationDate; + try { + activationDate = await this.findActivationDate(suspendData) + } catch (e) { + return { + error: String(e) + } + } const newTask: CreatePauseCancelTaskDTO = { iccid: iccid[0], activation_date: activationDate, diff --git a/packages/sim-entrada-eventos/aplication/Sim.controller.ts b/packages/sim-entrada-eventos/aplication/Sim.controller.ts index 0c69c8b..9afe780 100644 --- a/packages/sim-entrada-eventos/aplication/Sim.controller.ts +++ b/packages/sim-entrada-eventos/aplication/Sim.controller.ts @@ -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) diff --git a/packages/sim-objenious-cron/index.ts b/packages/sim-objenious-cron/index.ts index 9153d39..9db6a95 100644 --- a/packages/sim-objenious-cron/index.ts +++ b/packages/sim-objenious-cron/index.ts @@ -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 = { @@ -44,6 +47,21 @@ async function startCron() { 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 () => { try { @@ -62,7 +80,12 @@ async function startCron() { } }, PERIODO_VOLCADO) - await volcadoLineasTask.loadLines() + + //await pauseTask.run() + const PERIODO_CANCELACIONES = 24 * 60 * 60 * 1000; + const clacelacionesInterval = setInterval(async () => { + await pauseTask.run() + }, PERIODO_CANCELACIONES) } diff --git a/packages/sim-objenious-cron/tasks/check_pause_terminate.ts b/packages/sim-objenious-cron/tasks/check_pause_terminate.ts index 7f85fa5..6d9da8b 100644 --- a/packages/sim-objenious-cron/tasks/check_pause_terminate.ts +++ b/packages/sim-objenious-cron/tasks/check_pause_terminate.ts @@ -11,7 +11,7 @@ const logger = } -export class TaskPauseTerminate { +export class PauseTerminateTask { constructor( private readonly objeniousRepo: ObjeniousOperationsRepository, private readonly pauseRepo: PauseCancelTaskRepository, diff --git a/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts b/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts index 68d0009..4db3951 100644 --- a/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts +++ b/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts @@ -36,7 +36,7 @@ export class ObjeniousOperationsRepository implements IOperationsRepository { "identifier.identifiers": identifiers.toString() } - const req = this.http.client.get(path, { + const req = this.http.client.get(path, { params: params }) @@ -48,7 +48,7 @@ export class ObjeniousOperationsRepository implements IOperationsRepository { } } - const lines = res.data.data + const lines = res.data.data.content return { data: lines -- 2.49.1 From 5ea5939e3af930917ec5ae37e14479e3c52b7fce Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Thu, 9 Apr 2026 09:08:11 +0200 Subject: [PATCH 08/10] Bug de finaliazacion de tareas erroneas --- packages/sim-objenious-cron/tasks/check_objenious_request.ts | 3 ++- .../sim-shared/infrastructure/ObjeniousOperationRepository.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/sim-objenious-cron/tasks/check_objenious_request.ts b/packages/sim-objenious-cron/tasks/check_objenious_request.ts index 05ba66b..ed33bb8 100644 --- a/packages/sim-objenious-cron/tasks/check_objenious_request.ts +++ b/packages/sim-objenious-cron/tasks/check_objenious_request.ts @@ -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 ) { diff --git a/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts b/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts index 4db3951..5010326 100644 --- a/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts +++ b/packages/sim-shared/infrastructure/ObjeniousOperationRepository.ts @@ -171,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`; -- 2.49.1 From 047669bab25a60d3597a3bac5637fb0d1d1fb6d6 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Thu, 9 Apr 2026 11:53:49 +0200 Subject: [PATCH 09/10] Acabados de corregir bugs --- .../aplication/Sim.controller.ts | 1 - .../aplication/Sim.usecases.ts | 5 +- .../PauseCancelTaskRepository.test.ts | 11 +- .../PauseCancelTaskRepository.ts | 6 +- packages/sim-objenious-cron/index.ts | 4 +- .../tasks/check_pause_terminate.ts | 108 ++++++++++++------ 6 files changed, 80 insertions(+), 55 deletions(-) diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts index 800cf98..08bc354 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts @@ -164,7 +164,6 @@ export class SimController { public suspend() { return async (msg: ConsumeMessage) => { let msgData; - console.log("Consumiendo susension", msg) try { msgData = this.validateMsg(msg) as SimEvents.pause } catch (e) { diff --git a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts index 13c0b1a..bd92c96 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts @@ -273,7 +273,6 @@ export class SimUseCases { */ public stage_suspend(suspendData: ActionData): () => Promise> { return async (): Promise> => { - console.log("Suspend action data", suspendData) const correlation_id = suspendData.correlation_id const iccid = suspendData.identifier.identifiers @@ -300,7 +299,7 @@ export class SimUseCases { activation_date: activationDate, next_check: undefined, // Que se haga instantaneamente al ser la primera operation_type: "suspend", - actionData: suspendData + actiondata: suspendData } const taskCreated = await this.pauseRepository.addTask(newTask) @@ -340,7 +339,7 @@ export class SimUseCases { activation_date: activationDate, next_check: undefined, // Que se haga instantaneamente al ser la primera operation_type: "terminate", - actionData: terminateData + actiondata: terminateData } const taskCreated = await this.pauseRepository.addTask(newTask) diff --git a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts index 9640031..4a63a3e 100644 --- a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts +++ b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts @@ -8,7 +8,7 @@ const testTask: CreatePauseCancelTaskDTO = { operation_type: "suspend", activation_date: new Date(), next_check: new Date(), - actionData: { + actiondata: { dueDate: new Date().toString(), correlation_id: "12223", identifier: { @@ -19,20 +19,14 @@ const testTask: CreatePauseCancelTaskDTO = { } describe("Test PauseCancelTaskRepository - DB", () => { - function clean() { - - } const createdIds: number[] = []; - const pauseRepo = new PauseCancelTaskRepository(postgrClient) before(() => { - }) after(() => { - }) it("Should create a task", async () => { @@ -66,12 +60,13 @@ describe("Test PauseCancelTaskRepository - DB", () => { }) 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) + console.log("--> ", pending.data[0]) }) }) diff --git a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts index 30ee561..129ea6e 100644 --- a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts +++ b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts @@ -13,10 +13,10 @@ export type PauseCancelTask = { next_check?: Date | null; completed_date?: Date | null; error?: string | null; - actionData: ActionData + actiondata: ActionData } -export type CreatePauseCancelTaskDTO = Pick +export type CreatePauseCancelTaskDTO = Pick export type UpdatePauseCancelTaskDTO = Pick export type FinishPauseCancelTaskDTO = Pick @@ -62,7 +62,7 @@ export class PauseCancelTaskRepository { RETURNING *; `; try { - const values = [task.iccid, task.activation_date, task.next_check, task.operation_type, JSON.stringify(task.actionData)]; + const values = [task.iccid, task.activation_date, task.next_check, task.operation_type, JSON.stringify(task.actiondata)]; const res: QueryResult = await this.pgClient.query(sql, values); return { data: res.rows[0] diff --git a/packages/sim-objenious-cron/index.ts b/packages/sim-objenious-cron/index.ts index 9db6a95..e325b98 100644 --- a/packages/sim-objenious-cron/index.ts +++ b/packages/sim-objenious-cron/index.ts @@ -81,8 +81,8 @@ async function startCron() { }, PERIODO_VOLCADO) - //await pauseTask.run() - const PERIODO_CANCELACIONES = 24 * 60 * 60 * 1000; + await pauseTask.run() + const PERIODO_CANCELACIONES = 60 * 60 * 1000; const clacelacionesInterval = setInterval(async () => { await pauseTask.run() }, PERIODO_CANCELACIONES) diff --git a/packages/sim-objenious-cron/tasks/check_pause_terminate.ts b/packages/sim-objenious-cron/tasks/check_pause_terminate.ts index 6d9da8b..f588ef6 100644 --- a/packages/sim-objenious-cron/tasks/check_pause_terminate.ts +++ b/packages/sim-objenious-cron/tasks/check_pause_terminate.ts @@ -6,8 +6,8 @@ import { OrderRepository } from "packages/sim-shared/infrastructure/OrderReposit const logger = { - log: (...data: any[]) => console.log("[i] [TaskPauseTerminate]", data), - error: (...data: any[]) => console.error("[x] [TaskPauseTerminate] ", data), + log: (...data: any[]) => console.log("[i] [TaskPauseTerminate]", ...data), + error: (...data: any[]) => console.error("[x] [TaskPauseTerminate] ", ...data), } @@ -25,6 +25,10 @@ export class PauseTerminateTask { 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") @@ -37,6 +41,11 @@ export class PauseTerminateTask { } 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) @@ -68,6 +77,8 @@ export class PauseTerminateTask { 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") @@ -101,53 +112,74 @@ export class PauseTerminateTask { dueDate.setMinutes(new Date().getMinutes() + 15) const operacionTipo = operacion.operation_type - const actionData = operacion.actionData - const correlation_id = operacion.actionData.correlation_id - actionData.dueDate = dueDate.toString() + const actionData = operacion.actiondata + const correlation_id = operacion.actiondata.correlation_id + actionData.dueDate = dueDate.toISOString() - if (linea.status.billingStatus == "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; - } + 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) + 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: result.error + error: "billingStatus is CANCELED" }) 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 }) + 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) - // TODO: SUSPENDED Y TERMINATED + } } - - logger.log("Finalizado con exito proceso de comprobacion de lineas en pausa o canceladas") + finExito() } catch (e) { finError(e) } -- 2.49.1 From 031f5d5cf0cbc6f3b0744f81cda5aae3fa93a015 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Thu, 9 Apr 2026 11:59:06 +0200 Subject: [PATCH 10/10] nombre de columna con mayus --- .../database/migrations/1.2.0_Cola-pausa-cancelacion.sql | 2 +- .../sim-consumidor-objenious/aplication/Sim.usecases.ts | 4 ++-- .../infrastructure/PauseCancelTaskRepository.test.ts | 2 +- .../infrastructure/PauseCancelTaskRepository.ts | 6 +++--- packages/sim-objenious-cron/tasks/check_pause_terminate.ts | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql b/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql index 9a39e62..3d0791e 100644 --- a/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql +++ b/deployment/database/migrations/1.2.0_Cola-pausa-cancelacion.sql @@ -21,7 +21,7 @@ CREATE TABLE IF NOT EXISTS pause_cancel_tasks ( completed_date TIMESTAMPTZ, -- Cuando se ha completado, para bien o mal. error TEXT, - actionData JSONB -- datos de la operacion original. + action_data JSONB -- datos de la operacion original. ); -- Indice de las tareas que no han terminado diff --git a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts index bd92c96..119b0f9 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts @@ -299,7 +299,7 @@ export class SimUseCases { activation_date: activationDate, next_check: undefined, // Que se haga instantaneamente al ser la primera operation_type: "suspend", - actiondata: suspendData + action_data: suspendData } const taskCreated = await this.pauseRepository.addTask(newTask) @@ -339,7 +339,7 @@ export class SimUseCases { activation_date: activationDate, next_check: undefined, // Que se haga instantaneamente al ser la primera operation_type: "terminate", - actiondata: terminateData + action_data: terminateData } const taskCreated = await this.pauseRepository.addTask(newTask) diff --git a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts index 4a63a3e..a1e48e4 100644 --- a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts +++ b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.test.ts @@ -8,7 +8,7 @@ const testTask: CreatePauseCancelTaskDTO = { operation_type: "suspend", activation_date: new Date(), next_check: new Date(), - actiondata: { + action_data: { dueDate: new Date().toString(), correlation_id: "12223", identifier: { diff --git a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts index 129ea6e..219dac4 100644 --- a/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts +++ b/packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.ts @@ -13,10 +13,10 @@ export type PauseCancelTask = { next_check?: Date | null; completed_date?: Date | null; error?: string | null; - actiondata: ActionData + action_data: ActionData } -export type CreatePauseCancelTaskDTO = Pick +export type CreatePauseCancelTaskDTO = Pick export type UpdatePauseCancelTaskDTO = Pick export type FinishPauseCancelTaskDTO = Pick @@ -62,7 +62,7 @@ export class PauseCancelTaskRepository { RETURNING *; `; try { - const values = [task.iccid, task.activation_date, task.next_check, task.operation_type, JSON.stringify(task.actiondata)]; + const values = [task.iccid, task.activation_date, task.next_check, task.operation_type, JSON.stringify(task.action_data)]; const res: QueryResult = await this.pgClient.query(sql, values); return { data: res.rows[0] diff --git a/packages/sim-objenious-cron/tasks/check_pause_terminate.ts b/packages/sim-objenious-cron/tasks/check_pause_terminate.ts index f588ef6..a43a832 100644 --- a/packages/sim-objenious-cron/tasks/check_pause_terminate.ts +++ b/packages/sim-objenious-cron/tasks/check_pause_terminate.ts @@ -112,8 +112,8 @@ export class PauseTerminateTask { dueDate.setMinutes(new Date().getMinutes() + 15) const operacionTipo = operacion.operation_type - const actionData = operacion.actiondata - const correlation_id = operacion.actiondata.correlation_id + const actionData = operacion.action_data + const correlation_id = operacion.action_data.correlation_id actionData.dueDate = dueDate.toISOString() switch (linea.status.billingStatus) { -- 2.49.1