Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
uses: actions/checkout@v6

- name: Set up pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6

- name: Set up Node.js
uses: actions/setup-node@v6
Expand All @@ -38,8 +38,14 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Verify application
run: pnpm verify
- name: Typecheck
run: pnpm typecheck

- name: Run unit tests
run: pnpm test

- name: Build application
run: pnpm build

- name: Set up Terraform
uses: hashicorp/setup-terraform@v4
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/deploy-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ env:
ECR_REGISTRY: 401696231252.dkr.ecr.ap-southeast-1.amazonaws.com
API_REPOSITORY: mem9-node-prod-api
WORKER_REPOSITORY: mem9-node-prod-worker
API_HEALTH_URL: https://napi.mem9.ai/health/ready
TF_STATE_BUCKET: mem9-node-prod-terraform-state
TF_STATE_KMS_KEY_ARN: arn:aws:kms:ap-southeast-1:401696231252:key/4cd4c7a1-c1a6-47ad-9205-548678b84d86
TERRAFORM_VERSION: 1.15.4
Expand Down Expand Up @@ -242,6 +243,20 @@ jobs:
--services "$API_SERVICE" "$WORKER_SERVICE" \
--region "$AWS_REGION"

- name: Verify production API health
run: |
curl \
--fail-with-body \
--silent \
--show-error \
--retry 10 \
--retry-delay 5 \
--retry-all-errors \
--connect-timeout 5 \
--max-time 10 \
"$API_HEALTH_URL" \
| jq -e '.status == "ready"'

- name: Record successful release
run: |
API_TASK_DEFINITION=$(terraform -chdir="$TF_WORKING_DIR" output -raw api_task_definition_arn)
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/deep-analysis-source-preparation.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ describe('deep analysis source preparation service', () => {
enqueueLlmMessage: jest.fn(),
} as never,
);
const errorSpy = jest
.spyOn(service['logger'], 'error')
.mockImplementation(() => undefined);

await service.prepareAndEnqueue({
reportId: 'dar_1',
Expand All @@ -94,5 +97,6 @@ describe('deep analysis source preparation service', () => {
errorCode: 'DEEP_ANALYSIS_SOURCE_PREP_FAILED',
}),
);
expect(errorSpy).toHaveBeenCalled();
});
});
4 changes: 4 additions & 0 deletions apps/api/src/memory-analysis/memory-analysis.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,9 @@ describe('memory analysis report service', () => {
throw new Error('sqs down');
}),
});
const errorSpy = jest
.spyOn(service['logger'], 'error')
.mockImplementation(() => undefined);

await expect(service.createReport(reportContext, {
createdAfter: '2026-06-01T00:00:00.000Z',
Expand All @@ -459,6 +462,7 @@ describe('memory analysis report service', () => {
reportStage: 'failed',
failCode: 'MEMORY_ANALYSIS_QUEUE_ENQUEUE_FAILED',
}));
expect(errorSpy).toHaveBeenCalled();
});

it('lists memory analysis reports by type', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1122,12 +1122,18 @@ describe('deep analysis report processor service', () => {
createConfig() as never,
);

const errorSpy = jest
.spyOn(processor['logger'], 'error')
.mockImplementation(() => undefined);

await processor.process({
messageType: 'deep_report',
reportId: 'dar_trim_failure',
traceId: 'trace_trim_failure',
});

expect(errorSpy).toHaveBeenCalled();

expect(repository.updateDeepAnalysisReport).toHaveBeenLastCalledWith(
'dar_trim_failure',
expect.objectContaining({
Expand Down
4 changes: 4 additions & 0 deletions apps/worker/src/memory-analysis-report-runner.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ describe('MemoryAnalysisReportRunnerService', () => {
repository as never,
new FakeRedis() as never,
);
const errorSpy = jest
.spyOn(runner['logger'], 'error')
.mockImplementation(() => undefined);

await runner.generateReport(reportContext, 1, {
createdAfter: '2026-06-01T00:00:00.000Z',
Expand All @@ -131,6 +134,7 @@ describe('MemoryAnalysisReportRunnerService', () => {
failCode: 'QWEN_NOT_CONFIGURED',
failReason: 'Qwen API key or model is not configured.',
}));
expect(errorSpy).toHaveBeenCalled();
});

it('uses the cache version for period summary cache lookups and writes', async () => {
Expand Down
8 changes: 8 additions & 0 deletions apps/worker/src/sqs-consumer.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ describe('sqs consumer service', () => {
} as never,
createConfig(),
);
const errorSpy = jest
.spyOn(consumer['logger'], 'error')
.mockImplementation(() => undefined);
const setIntervalSpy = jest.spyOn(global, 'setInterval');

consumer['running'] = true;
Expand All @@ -140,6 +143,7 @@ describe('sqs consumer service', () => {
expect(Sentry.captureException).toHaveBeenCalledWith(
expect.any(SyntaxError),
);
expect(errorSpy).toHaveBeenCalled();
});

it('keeps consuming after one processor failure', async () => {
Expand Down Expand Up @@ -205,6 +209,9 @@ describe('sqs consumer service', () => {
} as never,
createConfig(),
);
const errorSpy = jest
.spyOn(consumer['logger'], 'error')
.mockImplementation(() => undefined);

consumer['running'] = true;
await consumer['consumeBatchLoop']();
Expand All @@ -214,6 +221,7 @@ describe('sqs consumer service', () => {
expect(queue.deleteMessage).toHaveBeenCalledWith('rh_2');
expect(Sentry.captureException).toHaveBeenCalledTimes(1);
expect(Sentry.captureException).toHaveBeenCalledWith(expect.any(Error));
expect(errorSpy).toHaveBeenCalled();
});

it('dispatches memory analysis report llm messages', async () => {
Expand Down