@benup/bensdk 1.11.16 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/lib/schemas/action.schema.d.ts +3423 -1190
- package/bin/lib/schemas/benefit-definition.schema.d.ts +94 -2
- package/bin/src/cli/init.js +224 -7
- package/bin/src/cli/init.js.map +1 -1
- package/bin/src/cli/templates/bensdk-base/.benSDKIgnore.template +3 -2
- package/bin/src/cli/templates/bensdk-base/.gitignore.template +4 -1
- package/bin/src/cli/templates/bensdk-base/README.md.template +52 -74
- package/bin/src/cli/templates/bensdk-base/context.config.ts.template +97 -21
- package/bin/src/cli/templates/bensdk-base/dev-test.example.json +22 -0
- package/bin/src/cli/templates/bensdk-base/docs/DEV_SERVER.md +201 -0
- package/bin/src/cli/templates/bensdk-docs/update-readme.ts +96 -0
- package/bin/src/cli/templates/bensdk-local-server/app.ts +234 -33
- package/bin/src/cli/templates/package.template.json +4 -1
- package/package.json +1 -1
|
@@ -1,110 +1,88 @@
|
|
|
1
|
-
|
|
1
|
+
<img src="ICON.png" alt="BenefitID" width="120">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# bensdk - BenefitID
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
## 🔧 Estrutura do Projeto
|
|
5
|
+
## Estrutura do Projeto
|
|
9
6
|
|
|
10
7
|
```
|
|
11
8
|
├── src/
|
|
12
|
-
│ ├──
|
|
13
|
-
│
|
|
14
|
-
|
|
15
|
-
├──
|
|
16
|
-
├──
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
│ ├── benefit-definition.ts # Definição do benefício + máquina de estados
|
|
10
|
+
│ ├── handlers/ # Handlers executados pelo integrador
|
|
11
|
+
│ └── lib/ # Utilitários e mocks
|
|
12
|
+
├── bin/
|
|
13
|
+
│ ├── cli/ # CLI para geração de handlers e testes
|
|
14
|
+
│ └── server/ # Servidor local para testes (npm run dev:start)
|
|
15
|
+
├── ICON.png # Ícone do benefício
|
|
16
|
+
├── .benSDKIgnore # Ignora arquivos no empacotamento
|
|
17
|
+
└── dev-test.example.json # Modelo de credenciais para testes locais
|
|
19
18
|
```
|
|
20
19
|
|
|
20
|
+
## Conceitos Básicos
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
### 📤 Actions
|
|
22
|
+
### Actions
|
|
25
23
|
|
|
26
|
-
As **Actions** são
|
|
24
|
+
As **Actions** são os eventos enviados pelo integrador ao seu BenSDK. Cada action representa uma operação sobre um benefício (ex: concessão, revogação, recarga) e é processada de acordo com a **máquina de estados** definida em `src/benefit-definition.ts`.
|
|
27
25
|
|
|
28
|
-
###
|
|
26
|
+
### Handlers
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
Cada **estado** da máquina de estados corresponde a um arquivo de handler em `src/handlers/`. O handler recebe:
|
|
31
29
|
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
|
|
30
|
+
- `message`: contexto da mensagem recebida da fila
|
|
31
|
+
- `action`: dados da operação (funcionário, empresa, eligibilityOptions, ctx compartilhado)
|
|
32
|
+
- `ctx`: contexto de execução (APIs, logger, benefitDefinition)
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
- `request_remove_employee.handler.ts` → Quando um funcionário for removido
|
|
38
|
-
- `sync_existing_grant.handler.ts` → Verifica se o funcionário **já possui** o benefício
|
|
39
|
-
- `sync_existing_revoke.handler.ts` → Verifica se o funcionário **já foi removido**
|
|
34
|
+
### ctx: Contexto compartilhado entre handlers
|
|
40
35
|
|
|
41
|
-
|
|
36
|
+
O campo `ctx` dentro de `action` é um objeto de estado acumulado ao longo de um fluxo. Use-o para passar dados entre etapas, por exemplo, um ID retornado pela API do parceiro que será necessário em handlers subsequentes.
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
const handler: StateHandler<
|
|
45
|
-
ActionGrant,
|
|
46
|
-
ActionLogGrant['REQUEST_CREATE_EMPLOYEE'],
|
|
47
|
-
ActionCtx
|
|
48
|
-
> = async (message, action, ctx) => {
|
|
49
|
-
// Aqui você chama a API do seu benefício, por exemplo.
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
handlerResponse: { response: 'ACK' },
|
|
53
|
-
action: {
|
|
54
|
-
state: stateMachine.next,
|
|
55
|
-
ctx: {},
|
|
56
|
-
logs: { date: new Date().toISOString() },
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export default handler;
|
|
62
|
-
|
|
63
|
-
```
|
|
38
|
+
## Fluxos
|
|
64
39
|
|
|
65
|
-
###
|
|
40
|
+
### Concessão de Benefício (GRANT)
|
|
66
41
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
#### ✅ Concessão de Benefício (GRANT)
|
|
70
|
-
|
|
71
|
-
Nesse fluxo, o integrador:
|
|
72
|
-
1. Verifica se o funcionário já está no benefício (`SYNC_EXISTING_GRANT` - Executando o handler `sync_existing_grant.handler.ts`).
|
|
73
|
-
2. Caso não esteja, solicita sua inclusão (`REQUEST_CREATE_EMPLOYEE` - Executando o handler `request_create_employee.handler.ts` ).
|
|
74
|
-
|
|
75
|
-
```mermaid
|
|
42
|
+
```mermaid
|
|
76
43
|
flowchart TD
|
|
77
|
-
START([
|
|
78
|
-
REQUESTED_GRANT -->|next| SYNC_EXISTING_GRANT
|
|
44
|
+
START([Início]) --> REQUESTED_GRANT
|
|
45
|
+
REQUESTED_GRANT -->|next| SYNC_EXISTING_GRANT
|
|
79
46
|
SYNC_EXISTING_GRANT -->|next| REQUEST_CREATE_EMPLOYEE
|
|
80
47
|
SYNC_EXISTING_GRANT -->|skip| GRANTED
|
|
81
48
|
REQUEST_CREATE_EMPLOYEE -->|next| GRANTED
|
|
82
49
|
REQUEST_CREATE_EMPLOYEE -->|fail| FAILED_TO_GRANT
|
|
83
50
|
|
|
51
|
+
GRANTED([GRANTED ✅])
|
|
52
|
+
FAILED_TO_GRANT([FAILED_TO_GRANT ❌])
|
|
84
53
|
```
|
|
85
54
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Nesse fluxo, o integrador:
|
|
89
|
-
1. Verifica se o funcionário já foi removido do benefício (`SYNC_EXISTING_REVOKE`).
|
|
90
|
-
2. Caso não tenha sido, solicita sua revogação (`REQUEST_REMOVE_EMPLOYEE`).
|
|
55
|
+
### Revogação de Benefício (REVOKE)
|
|
91
56
|
|
|
92
|
-
```mermaid
|
|
57
|
+
```mermaid
|
|
93
58
|
flowchart TD
|
|
94
|
-
START([
|
|
95
|
-
REQUESTED_REVOKE -->|next| SYNC_EXISTING_REVOKE
|
|
59
|
+
START([Início]) --> REQUESTED_REVOKE
|
|
60
|
+
REQUESTED_REVOKE -->|next| SYNC_EXISTING_REVOKE
|
|
96
61
|
SYNC_EXISTING_REVOKE -->|next| REQUEST_REMOVE_EMPLOYEE
|
|
97
62
|
SYNC_EXISTING_REVOKE -->|skip| REVOKED
|
|
98
63
|
REQUEST_REMOVE_EMPLOYEE -->|next| REVOKED
|
|
99
64
|
REQUEST_REMOVE_EMPLOYEE -->|fail| FAILED_TO_REVOKE
|
|
100
65
|
|
|
66
|
+
REVOKED([REVOKED ✅])
|
|
67
|
+
FAILED_TO_REVOKE([FAILED_TO_REVOKE ❌])
|
|
101
68
|
```
|
|
102
69
|
|
|
103
|
-
##
|
|
70
|
+
## Comandos
|
|
104
71
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
72
|
+
| Comando | Descricao |
|
|
73
|
+
|---------|-----------|
|
|
74
|
+
| `npm run dev:start` | Inicia o servidor local com autenticacao real contra a API do parceiro. [Ver documentacao](docs/DEV_SERVER.md) |
|
|
75
|
+
| `npm run test` | Executa os testes unitarios com Vitest |
|
|
76
|
+
| `npm run generate` | Gera handlers e testes a partir do `benefit-definition.ts` |
|
|
77
|
+
| `npm run start` | Abre o painel visual da maquina de estados |
|
|
78
|
+
| `npm run lint` | Valida tipos TypeScript, formatacao e linting |
|
|
79
|
+
| `npm run build` | Compila o projeto para `dist/` |
|
|
109
80
|
|
|
110
|
-
|
|
81
|
+
## Publicação
|
|
82
|
+
|
|
83
|
+
1. Instale o [maria-cli](https://github.com/benup-dev/maria) seguindo as instrucões do repositório.
|
|
84
|
+
2. Execute o comando abaixo na raiz do projeto:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
maria benefit deploy -r <link-ssh-do-repositorio> -n <benefitID>
|
|
88
|
+
```
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { StateHandlerCtx } from '@benup/bensdk/bin/lib/types/state-handler.types.d.ts';
|
|
2
|
+
import type { BenefitDefinition } from '@benup/bensdk/bin/lib/types/benefit-definition.types';
|
|
3
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
4
|
import AxiosMockAdapter from 'axios-mock-adapter';
|
|
3
|
-
import {
|
|
5
|
+
import pino, { Logger } from 'pino';
|
|
4
6
|
|
|
5
|
-
export function createStateHandlerCtxMock(
|
|
7
|
+
export function createStateHandlerCtxMock(
|
|
8
|
+
benefitDefinition: BenefitDefinition,
|
|
9
|
+
logger: Logger = pino(),
|
|
10
|
+
disableMocks = false
|
|
11
|
+
) {
|
|
6
12
|
const benupAPI = axios.create({
|
|
7
13
|
validateStatus: () => true,
|
|
8
14
|
baseURL: 'http://localhost/benupAPI'
|
|
@@ -18,35 +24,105 @@ export function createStateHandlerCtxMock(benefitDefinition, logger) {
|
|
|
18
24
|
baseURL: 'http://localhost/benefitsAPI'
|
|
19
25
|
});
|
|
20
26
|
|
|
27
|
+
const benefitPartnerAPI = axios.create({
|
|
28
|
+
validateStatus: () => true,
|
|
29
|
+
baseURL: 'http://localhost/benefitPartnerAPI'
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// issueHelper mock: prints issues to the logger instead of sending them to a real service
|
|
33
|
+
// This way you can see issues raised by handlers during local development
|
|
34
|
+
const issuesHelper = {
|
|
35
|
+
sendIssue: async ({
|
|
36
|
+
title,
|
|
37
|
+
description,
|
|
38
|
+
fieldPath
|
|
39
|
+
}: {
|
|
40
|
+
title: string;
|
|
41
|
+
description: string;
|
|
42
|
+
fieldPath?: string;
|
|
43
|
+
}) => {
|
|
44
|
+
logger.warn(`[Issue] [${title}] ${description}${fieldPath ? ` → ${fieldPath}` : ''}`);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// When disableMocks=true the dev server will inject a real authenticated axios instance
|
|
49
|
+
// into ctxMock.benefitPartnerAPI. The mock here is a no-op passthrough function that can
|
|
50
|
+
// be applied to the authenticated instance if you need to stub specific endpoints
|
|
51
|
+
// (e.g. presigned URLs, file uploads) while keeping the rest as real API calls.
|
|
52
|
+
if (disableMocks) {
|
|
53
|
+
const apisMocks = {
|
|
54
|
+
benupAPI: new AxiosMockAdapter(benupAPI, { onNoMatch: 'throwException' }),
|
|
55
|
+
lgProxyAPI: new AxiosMockAdapter(lgProxyAPI, { onNoMatch: 'throwException' }),
|
|
56
|
+
benefitsAPI: new AxiosMockAdapter(benefitsAPI, { onNoMatch: 'throwException' }),
|
|
57
|
+
|
|
58
|
+
// Return a function so the dev server can apply mocks to the real authenticated instance.
|
|
59
|
+
// Use onNoMatch: 'passthrough' so real API calls are not blocked.
|
|
60
|
+
// Add any endpoint stubs here that you want to intercept even in real-call mode.
|
|
61
|
+
benefitPartnerAPI: (axiosInstance: AxiosInstance) => {
|
|
62
|
+
const mock = new AxiosMockAdapter(axiosInstance, { onNoMatch: 'passthrough' });
|
|
63
|
+
// Example: mock.onGet('/some-endpoint').reply(200, { ... });
|
|
64
|
+
return mock;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const ctxMock: StateHandlerCtx = {
|
|
69
|
+
rawMessage: {},
|
|
70
|
+
logger,
|
|
71
|
+
correlationIDs: {},
|
|
72
|
+
benefitsAPI,
|
|
73
|
+
lgProxyAPI,
|
|
74
|
+
benupAPI,
|
|
75
|
+
benefitDefinition,
|
|
76
|
+
benefitPartnerAPI,
|
|
77
|
+
// @ts-expect-error: universAPI intentionally omitted in mock context
|
|
78
|
+
universAPI: undefined,
|
|
79
|
+
issuesHelper
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return { ctxMock, apisMocks };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Full mock setup for unit testing (vitest). All API calls must be explicitly mocked.
|
|
21
86
|
const apisMocks = {
|
|
22
|
-
benupAPI: new AxiosMockAdapter(benupAPI, {
|
|
23
|
-
|
|
24
|
-
}),
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
87
|
+
benupAPI: new AxiosMockAdapter(benupAPI, { onNoMatch: 'throwException' }),
|
|
88
|
+
lgProxyAPI: new AxiosMockAdapter(lgProxyAPI, { onNoMatch: 'throwException' }),
|
|
89
|
+
benefitsAPI: new AxiosMockAdapter(benefitsAPI, { onNoMatch: 'throwException' }),
|
|
90
|
+
benefitPartnerAPI: new AxiosMockAdapter(benefitPartnerAPI, { onNoMatch: 'throwException' }),
|
|
91
|
+
|
|
92
|
+
// Reset all mock adapters between tests
|
|
93
|
+
resetMocks: function () {
|
|
94
|
+
Object.entries(this)
|
|
95
|
+
.filter(([_, value]) => value instanceof AxiosMockAdapter)
|
|
96
|
+
.forEach(([_, mockAdapter]) => {
|
|
97
|
+
(mockAdapter as AxiosMockAdapter).reset();
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// Restore all mock adapters (call in afterAll to clean up)
|
|
102
|
+
restoreAll: function () {
|
|
103
|
+
Object.entries(this)
|
|
104
|
+
.filter(([_, value]) => value instanceof AxiosMockAdapter)
|
|
105
|
+
.forEach(([_, mockAdapter]) => {
|
|
106
|
+
(mockAdapter as AxiosMockAdapter).restore();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
31
109
|
};
|
|
32
110
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
values: ['123']
|
|
36
|
-
});
|
|
37
|
-
|
|
111
|
+
// Set up your mock responses here for unit tests.
|
|
112
|
+
// Example: apisMocks.benefitPartnerAPI.onGet('/some-path').replyOnce(200, { ... });
|
|
38
113
|
|
|
39
114
|
const ctxMock: StateHandlerCtx = {
|
|
40
115
|
rawMessage: {},
|
|
41
|
-
|
|
42
116
|
logger,
|
|
43
|
-
|
|
44
117
|
correlationIDs: {},
|
|
45
|
-
|
|
46
118
|
benefitsAPI,
|
|
47
119
|
lgProxyAPI,
|
|
48
120
|
benupAPI,
|
|
49
|
-
benefitDefinition
|
|
121
|
+
benefitDefinition,
|
|
122
|
+
benefitPartnerAPI,
|
|
123
|
+
// @ts-expect-error: universAPI intentionally omitted in mock context
|
|
124
|
+
universAPI: undefined,
|
|
125
|
+
issuesHelper
|
|
50
126
|
};
|
|
51
127
|
|
|
52
128
|
return { ctxMock, apisMocks };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Copy this file to dev-test.json and fill in your real values. dev-test.json is gitignored.",
|
|
3
|
+
|
|
4
|
+
"credentials": {
|
|
5
|
+
"accountID": "FILL_ME",
|
|
6
|
+
"companyID": "FILL_ME",
|
|
7
|
+
"benefitID": "FILL_ME",
|
|
8
|
+
"connection": {
|
|
9
|
+
"url": "https://api.your-partner.com"
|
|
10
|
+
},
|
|
11
|
+
"secret": {
|
|
12
|
+
"username": "FILL_ME",
|
|
13
|
+
"password": "FILL_ME"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
"eligibilityOptions": {
|
|
18
|
+
"GRANT": {},
|
|
19
|
+
"RECHARGE": {},
|
|
20
|
+
"GRANT_DEPENDENT": {}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Servidor de Desenvolvimento Local: `npm run dev:start`
|
|
2
|
+
|
|
3
|
+
O `dev:start` sobe um servidor HTTP local (porta 3000) que permite testar qualquer handler diretamente contra a API real do parceiro, com autenticacao verdadeira e sem precisar de mocks.
|
|
4
|
+
|
|
5
|
+
Isso e especialmente util porque:
|
|
6
|
+
- Os dados que chegam da folha LG variam por cliente e nem sempre sao previsiveis nos testes unitarios.
|
|
7
|
+
- As APIs dos parceiros tem comportamentos nao documentados que so aparecem em chamadas reais.
|
|
8
|
+
- E possivel executar cada estado da maquina em sequencia, acumulando o `ctx` entre chamadas, simulando exatamente o que o integrador faria.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Pre-requisitos
|
|
13
|
+
|
|
14
|
+
Crie o arquivo `dev-test.json` na raiz do projeto a partir do modelo:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cp dev-test.example.json dev-test.json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Preencha com suas credenciais reais. A estrutura exata de `credentials` deve seguir o schema definido no `auth.handler` do seu `benefit-definition.ts`:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"credentials": {
|
|
25
|
+
"accountID": "...",
|
|
26
|
+
"companyID": "...",
|
|
27
|
+
"benefitID": "...",
|
|
28
|
+
"connection": {
|
|
29
|
+
"url": "https://api.seu-parceiro.com"
|
|
30
|
+
},
|
|
31
|
+
"secret": {
|
|
32
|
+
"username": "...",
|
|
33
|
+
"password": "..."
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"eligibilityOptions": {
|
|
37
|
+
"GRANT": {},
|
|
38
|
+
"RECHARGE": {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
> **Atencao:** `dev-test.json` esta no `.gitignore` e no `.benSDKIgnore`. **Nunca commite credenciais reais.**
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Iniciando o servidor
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm run dev:start
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Ao iniciar, o servidor exibe:
|
|
54
|
+
- Se as credenciais foram carregadas com sucesso.
|
|
55
|
+
- Quais acoes tem presets de `eligibilityOptions` configurados.
|
|
56
|
+
- Os endpoints disponiveis.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Endpoints
|
|
61
|
+
|
|
62
|
+
### `POST /`: Executar um handler
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"run": "SYNC_EXISTING_GRANT",
|
|
67
|
+
"payload": {
|
|
68
|
+
"target": {
|
|
69
|
+
"employee": { "name": "...", "cpf": "...", "birthdate": "..." },
|
|
70
|
+
"company": { "cnpj": "..." }
|
|
71
|
+
},
|
|
72
|
+
"employmentContract": {
|
|
73
|
+
"benefits": {
|
|
74
|
+
"BENEFIT_ID": { "options": {} }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"message": {},
|
|
79
|
+
"ctx": {}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
| Campo | Tipo | Descricao |
|
|
84
|
+
|-------|------|-----------|
|
|
85
|
+
| `run` | `string` | Nome do estado a executar (ex: `SYNC_EXISTING_GRANT` ou `sync_existing_grant`) |
|
|
86
|
+
| `payload` | `object` | Dados da action: target, employmentContract, eligibilityOptions opcionais |
|
|
87
|
+
| `message` | `object` | Contexto da mensagem, pode ser `{}` em testes locais |
|
|
88
|
+
| `ctx` | `object` | Contexto acumulado entre handlers (use o `ctx` retornado pelo handler anterior) |
|
|
89
|
+
|
|
90
|
+
### `POST /run-action`: Formato legado
|
|
91
|
+
|
|
92
|
+
Mesmo comportamento, mas utilizando os campos `requestPayload` e `currentContext` no lugar de `payload` e `ctx`.
|
|
93
|
+
|
|
94
|
+
### `GET /state-machine`
|
|
95
|
+
|
|
96
|
+
Retorna o objeto `stateMachine` definido em `benefit-definition.ts`.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Como funciona por dentro
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
Requisicao POST /
|
|
104
|
+
|
|
|
105
|
+
+- Carrega credenciais do dev-test.json
|
|
106
|
+
+- Chama benefitDefinition.auth.handler(credentials, { tokenHelper, logger })
|
|
107
|
+
| +- Token cacheado em memoria, so autentica uma vez por sessao
|
|
108
|
+
+- Injeta axios autenticado em ctx.benefitPartnerAPI
|
|
109
|
+
+- Mescla eligibilityOptions: dev-test.json < sobrescrito pelo corpo da requisicao
|
|
110
|
+
+- Valida eligibilityOptions e ctx contra os schemas do benefit-definition.ts
|
|
111
|
+
+- Executa o handler e retorna a resposta como JSON
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## eligibilityOptions: presets no `dev-test.json`
|
|
117
|
+
|
|
118
|
+
Para nao precisar repetir as `eligibilityOptions` em cada requisicao, defina-as no `dev-test.json`. O servidor identifica automaticamente a qual action type pertence o handler informado em `run` consultando a `stateMachine`:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"eligibilityOptions": {
|
|
123
|
+
"GRANT": {
|
|
124
|
+
"externalCompanyID": "12345",
|
|
125
|
+
"externalCardType": 1
|
|
126
|
+
},
|
|
127
|
+
"RECHARGE": {
|
|
128
|
+
"productCode": "VA"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- As options do `dev-test.json` sao as **default**.
|
|
135
|
+
- Se voce enviar `eligibilityOptions` no corpo da requisicao, eles **sobrescrevem** os do `dev-test.json`.
|
|
136
|
+
- Erros de validacao do schema aparecem como `WARN` no terminal e nao bloqueiam a execucao.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Testando um fluxo completo
|
|
141
|
+
|
|
142
|
+
Execute os handlers na ordem da maquina de estados, acumulando o `ctx`:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# 1. Verificar se o beneficiario ja existe
|
|
146
|
+
curl -s -X POST http://localhost:3000 \
|
|
147
|
+
-H "Content-Type: application/json" \
|
|
148
|
+
-d '{
|
|
149
|
+
"run": "SYNC_EXISTING_GRANT",
|
|
150
|
+
"payload": { "target": { "employee": { ... } } },
|
|
151
|
+
"ctx": {}
|
|
152
|
+
}' | jq .
|
|
153
|
+
|
|
154
|
+
# Exemplo de resposta:
|
|
155
|
+
# {
|
|
156
|
+
# "handlerResponse": { "response": "ACK" },
|
|
157
|
+
# "action": {
|
|
158
|
+
# "state": "REQUEST_CREATE_EMPLOYEE",
|
|
159
|
+
# "ctx": { "someInternalID": "abc123" },
|
|
160
|
+
# "logs": { ... }
|
|
161
|
+
# }
|
|
162
|
+
# }
|
|
163
|
+
|
|
164
|
+
# 2. Criar o beneficiario, passando o ctx retornado acima
|
|
165
|
+
curl -s -X POST http://localhost:3000 \
|
|
166
|
+
-H "Content-Type: application/json" \
|
|
167
|
+
-d '{
|
|
168
|
+
"run": "REQUEST_CREATE_EMPLOYEE",
|
|
169
|
+
"payload": { "target": { "employee": { ... } } },
|
|
170
|
+
"ctx": { "someInternalID": "abc123" }
|
|
171
|
+
}' | jq .
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
> **Dica:** Use `| jq .action.ctx` para extrair apenas o ctx da resposta e passar para o proximo handler.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Variaveis de ambiente
|
|
179
|
+
|
|
180
|
+
| Variavel | Valor | Efeito |
|
|
181
|
+
|----------|-------|--------|
|
|
182
|
+
| `NODE_ENV` | `production` | Mantem a validacao TLS ativa (por padrao, desabilitada em dev para suportar certificados self-signed) |
|
|
183
|
+
| `VERBOSE` | `true` | Loga o payload completo de cada requisicao no terminal |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Troubleshooting
|
|
188
|
+
|
|
189
|
+
**`dev-test.json not found`**
|
|
190
|
+
Crie o arquivo: `cp dev-test.example.json dev-test.json`
|
|
191
|
+
|
|
192
|
+
**`Authentication failed`**
|
|
193
|
+
Verifique se as credenciais em `dev-test.json` estao corretas e se a URL da API esta acessivel a partir da sua maquina.
|
|
194
|
+
|
|
195
|
+
**`Handler not found: sync_existing_grant.handler.ts`**
|
|
196
|
+
Verifique se:
|
|
197
|
+
1. O nome em `run` corresponde a um estado da `stateMachine` em `benefit-definition.ts`.
|
|
198
|
+
2. O arquivo `src/handlers/{estado}.handler.ts` existe (rode `npm run generate` para gera-lo).
|
|
199
|
+
|
|
200
|
+
**TLS / certificados self-signed**
|
|
201
|
+
Em ambientes com zero-trust ou proxies com certificados self-signed, o servidor ja desabilita a verificacao TLS automaticamente (exceto quando `NODE_ENV=production`).
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* npm run docs:update
|
|
4
|
+
*
|
|
5
|
+
* Reads the current benefit-definition.ts, parses the stateMachine and
|
|
6
|
+
* availableActions, and regenerates the ## Fluxos section in README.md
|
|
7
|
+
* with up-to-date Mermaid diagrams.
|
|
8
|
+
*
|
|
9
|
+
* Run manually or let the pre-commit hook handle it automatically.
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import benefitDefinition from '../../src/benefit-definition';
|
|
14
|
+
|
|
15
|
+
const ACTION_LABELS: Record<string, string> = {
|
|
16
|
+
GRANT: 'Concessão de Benefício (GRANT)',
|
|
17
|
+
REVOKE: 'Revogação de Benefício (REVOKE)',
|
|
18
|
+
RECHARGE: 'Recarga de Benefício (RECHARGE)',
|
|
19
|
+
DEDUCTION: 'Dedução (DEDUCTION)',
|
|
20
|
+
GRANT_DEPENDENT: 'Concessão para Dependente (GRANT_DEPENDENT)',
|
|
21
|
+
REVOKE_DEPENDENT: 'Revogação de Dependente (REVOKE_DEPENDENT)',
|
|
22
|
+
BEFORE_ALL_RECHARGE: 'Pré-processamento de Recargas (BEFORE_ALL_RECHARGE)',
|
|
23
|
+
AFTER_ALL_RECHARGE: 'Pós-processamento de Recargas (AFTER_ALL_RECHARGE)',
|
|
24
|
+
DEDUCTION_FILE_INGESTION: 'Ingestão de Arquivo de Deduções (DEDUCTION_FILE_INGESTION)'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function generateMermaidDiagram(
|
|
28
|
+
states: Record<string, Record<string, string>>
|
|
29
|
+
): string {
|
|
30
|
+
const lines: string[] = ['flowchart TD'];
|
|
31
|
+
|
|
32
|
+
// The starting state is usually REQUESTED_*, otherwise the first defined state
|
|
33
|
+
const firstState =
|
|
34
|
+
Object.keys(states).find((s) => s.startsWith('REQUESTED_')) ?? Object.keys(states)[0];
|
|
35
|
+
|
|
36
|
+
lines.push(` START([Início]) --> ${firstState}`);
|
|
37
|
+
|
|
38
|
+
// Track all states that appear as transition targets
|
|
39
|
+
const allTargets = new Set<string>();
|
|
40
|
+
|
|
41
|
+
for (const [state, transitions] of Object.entries(states)) {
|
|
42
|
+
for (const [transitionType, targetState] of Object.entries(
|
|
43
|
+
transitions as Record<string, string>
|
|
44
|
+
)) {
|
|
45
|
+
lines.push(` ${state} -->|${transitionType}| ${targetState}`);
|
|
46
|
+
allTargets.add(targetState);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Terminal states = appear as targets but are not defined as source states
|
|
51
|
+
const terminalStates = [...allTargets].filter((s) => !(s in states));
|
|
52
|
+
for (const terminal of terminalStates) {
|
|
53
|
+
const isFailure = terminal.includes('FAILED') || terminal.includes('FAIL');
|
|
54
|
+
lines.push(` ${terminal}([${terminal} ${isFailure ? '❌' : '✅'}])`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return '```mermaid\n' + lines.join('\n') + '\n```';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function generateActionSection(
|
|
61
|
+
action: string,
|
|
62
|
+
states: Record<string, Record<string, string>>
|
|
63
|
+
): string {
|
|
64
|
+
const label = ACTION_LABELS[action] ?? action;
|
|
65
|
+
const diagram = generateMermaidDiagram(states);
|
|
66
|
+
return `### ${label}\n\n${diagram}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const stateMachine = benefitDefinition.stateMachine as Record<
|
|
70
|
+
string,
|
|
71
|
+
Record<string, Record<string, string>>
|
|
72
|
+
>;
|
|
73
|
+
const availableActions = benefitDefinition.availableActions as readonly string[];
|
|
74
|
+
|
|
75
|
+
// Only generate diagrams for actions that are:
|
|
76
|
+
// 1. Listed in availableActions
|
|
77
|
+
// 2. Defined in stateMachine
|
|
78
|
+
const sections = Object.entries(stateMachine)
|
|
79
|
+
.filter(([action]) => availableActions.includes(action))
|
|
80
|
+
.map(([action, states]) => generateActionSection(action, states))
|
|
81
|
+
.join('\n\n');
|
|
82
|
+
|
|
83
|
+
const newFluxosSection = `## Fluxos\n\n${sections}`;
|
|
84
|
+
|
|
85
|
+
const readmePath = join(process.cwd(), 'README.md');
|
|
86
|
+
const readme = readFileSync(readmePath, 'utf-8');
|
|
87
|
+
|
|
88
|
+
// Replace everything between ## Fluxos and the next ## heading
|
|
89
|
+
const updated = readme.replace(/## Fluxos[\s\S]*?(?=\n## )/, newFluxosSection + '\n');
|
|
90
|
+
|
|
91
|
+
if (updated === readme) {
|
|
92
|
+
console.log('[benSDK] README.md: Fluxos section is already up to date');
|
|
93
|
+
} else {
|
|
94
|
+
writeFileSync(readmePath, updated);
|
|
95
|
+
console.log('[benSDK] README.md updated with current state machine diagrams');
|
|
96
|
+
}
|