78 lines
2.9 KiB
TypeScript
78 lines
2.9 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import type {
|
|
ConversationOutcomesRepository,
|
|
MessagesRepository,
|
|
SentMessageRecord,
|
|
SuccessfulMessagesRepository,
|
|
} from '../src/database/repositories.js';
|
|
import { recordConversationReply } from '../src/services/conversation-outcome-recorder.js';
|
|
import { createMockLogger } from './mockLogger.js';
|
|
|
|
function message(): SentMessageRecord {
|
|
return {
|
|
id: 1,
|
|
postId: 'post-1',
|
|
recipient: 'alice',
|
|
subject: 'alice',
|
|
message: 'Hello',
|
|
sentAt: 1,
|
|
gotResponse: false,
|
|
gotImage: false,
|
|
responseCheckedAt: null,
|
|
strategyId: 'direct',
|
|
chatRoomId: 'room-1',
|
|
subreddit: 'example',
|
|
};
|
|
}
|
|
|
|
function dependencies() {
|
|
return {
|
|
messages: { markGotResponse: vi.fn() } as unknown as MessagesRepository,
|
|
successful: {
|
|
insert: vi.fn(),
|
|
existsByMessage: vi.fn(() => false),
|
|
deleteBySentMessageId: vi.fn(() => false),
|
|
} as unknown as SuccessfulMessagesRepository,
|
|
outcomes: { record: vi.fn(), suppressRecipient: vi.fn() } as unknown as ConversationOutcomesRepository,
|
|
logger: createMockLogger(),
|
|
};
|
|
}
|
|
|
|
describe('recordConversationReply', () => {
|
|
it('records a first reply but excludes it from learning until engagement is reciprocal', () => {
|
|
const deps = dependencies();
|
|
expect(recordConversationReply(message(), 'Thanks!', { messageCount: 2, reciprocal: false }, deps)).toBe('unknown');
|
|
expect(deps.messages.markGotResponse).toHaveBeenCalledWith(1);
|
|
expect(deps.successful.insert).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('stores only reciprocal engaged conversations as learning examples', () => {
|
|
const deps = dependencies();
|
|
expect(recordConversationReply(message(), 'Tell me more', {
|
|
messageCount: 4, reciprocal: true, learningEligible: true,
|
|
}, deps)).toBe('engaged');
|
|
expect(deps.successful.insert).toHaveBeenCalledWith(expect.objectContaining({ sentMessageId: 1 }));
|
|
});
|
|
|
|
it('permanently suppresses a recipient after a boundary response', () => {
|
|
const deps = dependencies();
|
|
expect(recordConversationReply(message(), 'Stop messaging me', { messageCount: 2 }, deps)).toBe('boundary');
|
|
expect(deps.outcomes.suppressRecipient).toHaveBeenCalledWith('alice', 'boundary');
|
|
expect(deps.successful.insert).not.toHaveBeenCalled();
|
|
expect(deps.successful.deleteBySentMessageId).toHaveBeenCalledWith(1);
|
|
});
|
|
|
|
it('removes an earlier learning example when the full conversation later declines', () => {
|
|
const deps = dependencies();
|
|
expect(recordConversationReply(message(), "I'm good", {
|
|
messageCount: 8,
|
|
reciprocal: true,
|
|
learningEligible: true,
|
|
mediaWasRequested: true,
|
|
responses: ['Sure', 'Zero interest in sending you anything lol', "I'm good"],
|
|
}, deps)).toBe('declined');
|
|
expect(deps.successful.deleteBySentMessageId).toHaveBeenCalledWith(1);
|
|
expect(deps.successful.insert).not.toHaveBeenCalled();
|
|
});
|
|
});
|