test: cover multi-channel main and cross-channel name propagation
Replaces single-channel tests with multi-channel scenarios: - each channel can have its own main with admin context - non-main groups across channels get global template - custom name propagates to all channels and groups - user-modified CLAUDE.md preserved on re-registration - missing templates handled gracefully Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -263,6 +263,52 @@ describe('CLAUDE.md template copy', () => {
|
|||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
let groupsDir: string;
|
let groupsDir: string;
|
||||||
|
|
||||||
|
// Replicates register.ts template copy + name update logic
|
||||||
|
function simulateRegister(
|
||||||
|
folder: string,
|
||||||
|
isMain: boolean,
|
||||||
|
assistantName = 'Andy',
|
||||||
|
): void {
|
||||||
|
const folderDir = path.join(groupsDir, folder);
|
||||||
|
fs.mkdirSync(path.join(folderDir, 'logs'), { recursive: true });
|
||||||
|
|
||||||
|
// Template copy (register.ts lines 119-138)
|
||||||
|
const dest = path.join(folderDir, 'CLAUDE.md');
|
||||||
|
if (!fs.existsSync(dest)) {
|
||||||
|
const templatePath = isMain
|
||||||
|
? path.join(groupsDir, 'main', 'CLAUDE.md')
|
||||||
|
: path.join(groupsDir, 'global', 'CLAUDE.md');
|
||||||
|
if (fs.existsSync(templatePath)) {
|
||||||
|
fs.copyFileSync(templatePath, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name update across all groups (register.ts lines 140-165)
|
||||||
|
if (assistantName !== 'Andy') {
|
||||||
|
const mdFiles = fs
|
||||||
|
.readdirSync(groupsDir)
|
||||||
|
.map((d) => path.join(groupsDir, d, 'CLAUDE.md'))
|
||||||
|
.filter((f) => fs.existsSync(f));
|
||||||
|
|
||||||
|
for (const mdFile of mdFiles) {
|
||||||
|
let content = fs.readFileSync(mdFile, 'utf-8');
|
||||||
|
content = content.replace(/^# Andy$/m, `# ${assistantName}`);
|
||||||
|
content = content.replace(
|
||||||
|
/You are Andy/g,
|
||||||
|
`You are ${assistantName}`,
|
||||||
|
);
|
||||||
|
fs.writeFileSync(mdFile, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readGroupMd(folder: string): string {
|
||||||
|
return fs.readFileSync(
|
||||||
|
path.join(groupsDir, folder, 'CLAUDE.md'),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nanoclaw-register-test-'));
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nanoclaw-register-test-'));
|
||||||
groupsDir = path.join(tmpDir, 'groups');
|
groupsDir = path.join(tmpDir, 'groups');
|
||||||
@@ -283,123 +329,101 @@ describe('CLAUDE.md template copy', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('copies global template for non-main group', () => {
|
it('copies global template for non-main group', () => {
|
||||||
const folder = 'telegram_dev-team';
|
simulateRegister('telegram_dev-team', false);
|
||||||
const folderDir = path.join(groupsDir, folder);
|
|
||||||
fs.mkdirSync(path.join(folderDir, 'logs'), { recursive: true });
|
|
||||||
|
|
||||||
const dest = path.join(folderDir, 'CLAUDE.md');
|
const content = readGroupMd('telegram_dev-team');
|
||||||
const templatePath = path.join(groupsDir, 'global', 'CLAUDE.md');
|
expect(content).toContain('You are Andy');
|
||||||
|
expect(content).not.toContain('Admin Context');
|
||||||
// Replicate register.ts logic: copy template if dest doesn't exist
|
|
||||||
if (!fs.existsSync(dest)) {
|
|
||||||
if (fs.existsSync(templatePath)) {
|
|
||||||
fs.copyFileSync(templatePath, dest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(fs.existsSync(dest)).toBe(true);
|
|
||||||
expect(fs.readFileSync(dest, 'utf-8')).toContain('You are Andy');
|
|
||||||
// Should NOT contain main-specific content
|
|
||||||
expect(fs.readFileSync(dest, 'utf-8')).not.toContain('Admin Context');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('copies main template for main group', () => {
|
it('copies main template for main group', () => {
|
||||||
const folder = 'whatsapp_main';
|
simulateRegister('whatsapp_main', true);
|
||||||
const folderDir = path.join(groupsDir, folder);
|
|
||||||
fs.mkdirSync(path.join(folderDir, 'logs'), { recursive: true });
|
|
||||||
|
|
||||||
const dest = path.join(folderDir, 'CLAUDE.md');
|
expect(readGroupMd('whatsapp_main')).toContain('Admin Context');
|
||||||
const isMain = true;
|
|
||||||
const templatePath = isMain
|
|
||||||
? path.join(groupsDir, 'main', 'CLAUDE.md')
|
|
||||||
: path.join(groupsDir, 'global', 'CLAUDE.md');
|
|
||||||
|
|
||||||
if (!fs.existsSync(dest)) {
|
|
||||||
if (fs.existsSync(templatePath)) {
|
|
||||||
fs.copyFileSync(templatePath, dest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(fs.existsSync(dest)).toBe(true);
|
|
||||||
expect(fs.readFileSync(dest, 'utf-8')).toContain('Admin Context');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not overwrite existing CLAUDE.md', () => {
|
it('each channel can have its own main with admin context', () => {
|
||||||
const folder = 'slack_main';
|
simulateRegister('whatsapp_main', true);
|
||||||
const folderDir = path.join(groupsDir, folder);
|
simulateRegister('telegram_main', true);
|
||||||
fs.mkdirSync(folderDir, { recursive: true });
|
simulateRegister('slack_main', true);
|
||||||
|
simulateRegister('discord_main', true);
|
||||||
|
|
||||||
const dest = path.join(folderDir, 'CLAUDE.md');
|
for (const folder of [
|
||||||
fs.writeFileSync(dest, '# Custom\n\nUser-modified content.');
|
'whatsapp_main',
|
||||||
|
'telegram_main',
|
||||||
const templatePath = path.join(groupsDir, 'global', 'CLAUDE.md');
|
'slack_main',
|
||||||
if (!fs.existsSync(dest)) {
|
'discord_main',
|
||||||
if (fs.existsSync(templatePath)) {
|
]) {
|
||||||
fs.copyFileSync(templatePath, dest);
|
const content = readGroupMd(folder);
|
||||||
}
|
expect(content).toContain('Admin Context');
|
||||||
|
expect(content).toContain('You are Andy');
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(fs.readFileSync(dest, 'utf-8')).toContain('User-modified content');
|
|
||||||
expect(fs.readFileSync(dest, 'utf-8')).not.toContain('You are Andy');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates name in all groups/*/CLAUDE.md files', () => {
|
it('non-main groups across channels get global template', () => {
|
||||||
// Create a few group folders with CLAUDE.md
|
simulateRegister('whatsapp_main', true);
|
||||||
for (const folder of ['whatsapp_main', 'telegram_friends']) {
|
simulateRegister('telegram_friends', false);
|
||||||
const dir = path.join(groupsDir, folder);
|
simulateRegister('slack_engineering', false);
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
simulateRegister('discord_general', false);
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(dir, 'CLAUDE.md'),
|
expect(readGroupMd('whatsapp_main')).toContain('Admin Context');
|
||||||
'# Andy\n\nYou are Andy, a personal assistant.',
|
for (const folder of [
|
||||||
);
|
'telegram_friends',
|
||||||
|
'slack_engineering',
|
||||||
|
'discord_general',
|
||||||
|
]) {
|
||||||
|
const content = readGroupMd(folder);
|
||||||
|
expect(content).toContain('You are Andy');
|
||||||
|
expect(content).not.toContain('Admin Context');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const assistantName = 'Luna';
|
it('custom name propagates to all channels and groups', () => {
|
||||||
|
// Register multiple channels, last one sets custom name
|
||||||
|
simulateRegister('whatsapp_main', true);
|
||||||
|
simulateRegister('telegram_main', true);
|
||||||
|
simulateRegister('slack_devs', false);
|
||||||
|
// Final registration triggers name update across all
|
||||||
|
simulateRegister('discord_main', true, 'Luna');
|
||||||
|
|
||||||
// Replicate register.ts glob logic
|
for (const folder of [
|
||||||
const mdFiles = fs
|
'main',
|
||||||
.readdirSync(groupsDir)
|
'global',
|
||||||
.map((d) => path.join(groupsDir, d, 'CLAUDE.md'))
|
'whatsapp_main',
|
||||||
.filter((f) => fs.existsSync(f));
|
'telegram_main',
|
||||||
|
'slack_devs',
|
||||||
for (const mdFile of mdFiles) {
|
'discord_main',
|
||||||
let content = fs.readFileSync(mdFile, 'utf-8');
|
]) {
|
||||||
content = content.replace(/^# Andy$/m, `# ${assistantName}`);
|
const content = readGroupMd(folder);
|
||||||
content = content.replace(/You are Andy/g, `You are ${assistantName}`);
|
|
||||||
fs.writeFileSync(mdFile, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
// All CLAUDE.md files should be updated, including templates and groups
|
|
||||||
for (const folder of ['main', 'global', 'whatsapp_main', 'telegram_friends']) {
|
|
||||||
const content = fs.readFileSync(
|
|
||||||
path.join(groupsDir, folder, 'CLAUDE.md'),
|
|
||||||
'utf-8',
|
|
||||||
);
|
|
||||||
expect(content).toContain('# Luna');
|
expect(content).toContain('# Luna');
|
||||||
expect(content).toContain('You are Luna');
|
expect(content).toContain('You are Luna');
|
||||||
expect(content).not.toContain('Andy');
|
expect(content).not.toContain('Andy');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles missing template gracefully', () => {
|
it('does not overwrite user-modified CLAUDE.md', () => {
|
||||||
// Remove templates
|
simulateRegister('slack_main', true);
|
||||||
|
// User customizes the file
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(groupsDir, 'slack_main', 'CLAUDE.md'),
|
||||||
|
'# Custom\n\nUser-modified content.',
|
||||||
|
);
|
||||||
|
// Re-registering same folder (e.g. re-running /add-slack)
|
||||||
|
simulateRegister('slack_main', true);
|
||||||
|
|
||||||
|
const content = readGroupMd('slack_main');
|
||||||
|
expect(content).toContain('User-modified content');
|
||||||
|
expect(content).not.toContain('Admin Context');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles missing templates gracefully', () => {
|
||||||
fs.unlinkSync(path.join(groupsDir, 'global', 'CLAUDE.md'));
|
fs.unlinkSync(path.join(groupsDir, 'global', 'CLAUDE.md'));
|
||||||
fs.unlinkSync(path.join(groupsDir, 'main', 'CLAUDE.md'));
|
fs.unlinkSync(path.join(groupsDir, 'main', 'CLAUDE.md'));
|
||||||
|
|
||||||
const folder = 'discord_general';
|
simulateRegister('discord_general', false);
|
||||||
const folderDir = path.join(groupsDir, folder);
|
|
||||||
fs.mkdirSync(path.join(folderDir, 'logs'), { recursive: true });
|
|
||||||
|
|
||||||
const dest = path.join(folderDir, 'CLAUDE.md');
|
expect(
|
||||||
const templatePath = path.join(groupsDir, 'global', 'CLAUDE.md');
|
fs.existsSync(path.join(groupsDir, 'discord_general', 'CLAUDE.md')),
|
||||||
|
).toBe(false);
|
||||||
if (!fs.existsSync(dest)) {
|
|
||||||
if (fs.existsSync(templatePath)) {
|
|
||||||
fs.copyFileSync(templatePath, dest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No crash, no file created
|
|
||||||
expect(fs.existsSync(dest)).toBe(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user