galaxy-opc-plugin 0.1.1 → 0.2.1
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/index.ts +11 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/acquisition-management/SKILL.md +83 -0
- package/skills/ai-staff/SKILL.md +89 -0
- package/skills/asset-package/SKILL.md +142 -0
- package/skills/opb-canvas/SKILL.md +88 -0
- package/src/api/companies.ts +12 -1
- package/src/api/dashboard.ts +13 -2
- package/src/api/middleware.ts +44 -0
- package/src/api/rate-limiter.ts +79 -0
- package/src/api/routes.ts +19 -2
- package/src/db/sqlite-adapter.test.ts +304 -0
- package/src/db/sqlite-adapter.ts +1 -1
- package/src/opc/company-manager.test.ts +232 -0
- package/src/tools/acquisition-tool.test.ts +139 -0
- package/src/tools/acquisition-tool.ts +11 -8
- package/src/tools/asset-package-tool.ts +4 -4
- package/src/tools/finance-tool.test.ts +106 -0
- package/src/tools/finance-tool.ts +6 -4
- package/src/tools/hr-tool.test.ts +153 -0
- package/src/tools/hr-tool.ts +6 -4
- package/src/tools/investment-tool.ts +4 -4
- package/src/tools/legal-tool.ts +12 -7
- package/src/tools/lifecycle-tool.ts +5 -5
- package/src/tools/media-tool.ts +7 -5
- package/src/tools/monitoring-tool.ts +7 -5
- package/src/tools/opb-tool.ts +7 -7
- package/src/tools/opc-tool.ts +33 -23
- package/src/tools/procurement-tool.ts +4 -4
- package/src/tools/project-tool.ts +10 -6
- package/src/tools/schemas.ts +47 -43
- package/src/tools/staff-tool.ts +8 -6
- package/src/utils/tool-helper.ts +28 -2
- package/src/web/config-ui.ts +69 -15
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 星环OPC中心 — SqliteAdapter 单元测试
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
6
|
+
import { SqliteAdapter } from "./sqlite-adapter.js";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
|
|
11
|
+
describe("SqliteAdapter", () => {
|
|
12
|
+
let db: SqliteAdapter;
|
|
13
|
+
let dbPath: string;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
dbPath = path.join(os.tmpdir(), `opc-test-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
|
|
17
|
+
db = new SqliteAdapter(dbPath);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
db.close();
|
|
22
|
+
try { fs.unlinkSync(dbPath); } catch { /* ignore */ }
|
|
23
|
+
try { fs.unlinkSync(dbPath + "-wal"); } catch { /* ignore */ }
|
|
24
|
+
try { fs.unlinkSync(dbPath + "-shm"); } catch { /* ignore */ }
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// ── Companies ──────────────────────────────────────────────
|
|
28
|
+
describe("Companies CRUD", () => {
|
|
29
|
+
it("should create and retrieve a company", () => {
|
|
30
|
+
const company = db.createCompany({
|
|
31
|
+
name: "测试公司",
|
|
32
|
+
industry: "科技",
|
|
33
|
+
owner_name: "张三",
|
|
34
|
+
owner_contact: "13800138000",
|
|
35
|
+
status: "pending",
|
|
36
|
+
registered_capital: 100000,
|
|
37
|
+
description: "测试描述",
|
|
38
|
+
});
|
|
39
|
+
expect(company.id).toBeTruthy();
|
|
40
|
+
expect(company.name).toBe("测试公司");
|
|
41
|
+
|
|
42
|
+
const fetched = db.getCompany(company.id);
|
|
43
|
+
expect(fetched).not.toBeNull();
|
|
44
|
+
expect(fetched!.name).toBe("测试公司");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should list companies and filter by status", () => {
|
|
48
|
+
db.createCompany({ name: "A", industry: "IT", owner_name: "X", owner_contact: "", status: "pending", registered_capital: 0, description: "" });
|
|
49
|
+
db.createCompany({ name: "B", industry: "IT", owner_name: "Y", owner_contact: "", status: "active", registered_capital: 0, description: "" });
|
|
50
|
+
|
|
51
|
+
expect(db.listCompanies().length).toBe(2);
|
|
52
|
+
expect(db.listCompanies("pending").length).toBe(1);
|
|
53
|
+
expect(db.listCompanies("active").length).toBe(1);
|
|
54
|
+
expect(db.listCompanies("terminated").length).toBe(0);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should update a company", () => {
|
|
58
|
+
const c = db.createCompany({ name: "Old", industry: "IT", owner_name: "X", owner_contact: "", status: "pending", registered_capital: 0, description: "" });
|
|
59
|
+
const updated = db.updateCompany(c.id, { name: "New", industry: "金融" });
|
|
60
|
+
expect(updated).not.toBeNull();
|
|
61
|
+
expect(updated!.name).toBe("New");
|
|
62
|
+
expect(updated!.industry).toBe("金融");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should return null when updating non-existent company", () => {
|
|
66
|
+
expect(db.updateCompany("fake-id", { name: "X" })).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should delete a company", () => {
|
|
70
|
+
const c = db.createCompany({ name: "Del", industry: "IT", owner_name: "X", owner_contact: "", status: "pending", registered_capital: 0, description: "" });
|
|
71
|
+
expect(db.deleteCompany(c.id)).toBe(true);
|
|
72
|
+
expect(db.getCompany(c.id)).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should return false when deleting non-existent company", () => {
|
|
76
|
+
expect(db.deleteCompany("fake-id")).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ── Employees ──────────────────────────────────────────────
|
|
81
|
+
describe("Employees CRUD", () => {
|
|
82
|
+
it("should create and list employees", () => {
|
|
83
|
+
const c = db.createCompany({ name: "Emp Co", industry: "IT", owner_name: "X", owner_contact: "", status: "active", registered_capital: 0, description: "" });
|
|
84
|
+
const emp = db.createEmployee({ company_id: c.id, name: "小明", role: "finance", skills: "会计", status: "active" });
|
|
85
|
+
expect(emp.id).toBeTruthy();
|
|
86
|
+
expect(emp.name).toBe("小明");
|
|
87
|
+
|
|
88
|
+
const list = db.listEmployees(c.id);
|
|
89
|
+
expect(list.length).toBe(1);
|
|
90
|
+
expect(list[0].role).toBe("finance");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should get employee by id", () => {
|
|
94
|
+
const c = db.createCompany({ name: "Emp2", industry: "IT", owner_name: "X", owner_contact: "", status: "active", registered_capital: 0, description: "" });
|
|
95
|
+
const emp = db.createEmployee({ company_id: c.id, name: "小红", role: "hr", skills: "招聘", status: "active" });
|
|
96
|
+
const found = db.getEmployee(emp.id);
|
|
97
|
+
expect(found).not.toBeNull();
|
|
98
|
+
expect(found!.name).toBe("小红");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should return null for non-existent employee", () => {
|
|
102
|
+
expect(db.getEmployee("fake")).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ── Transactions ───────────────────────────────────────────
|
|
107
|
+
describe("Transactions CRUD", () => {
|
|
108
|
+
let companyId: string;
|
|
109
|
+
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
const c = db.createCompany({ name: "Tx Co", industry: "IT", owner_name: "X", owner_contact: "", status: "active", registered_capital: 0, description: "" });
|
|
112
|
+
companyId = c.id;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should create and retrieve a transaction", () => {
|
|
116
|
+
const tx = db.createTransaction({
|
|
117
|
+
company_id: companyId,
|
|
118
|
+
type: "income",
|
|
119
|
+
category: "service_income",
|
|
120
|
+
amount: 5000,
|
|
121
|
+
description: "咨询服务",
|
|
122
|
+
counterparty: "客户A",
|
|
123
|
+
transaction_date: "2025-01-15",
|
|
124
|
+
});
|
|
125
|
+
expect(tx.id).toBeTruthy();
|
|
126
|
+
expect(tx.amount).toBe(5000);
|
|
127
|
+
expect(tx.type).toBe("income");
|
|
128
|
+
|
|
129
|
+
const fetched = db.getTransaction(tx.id);
|
|
130
|
+
expect(fetched).not.toBeNull();
|
|
131
|
+
expect(fetched!.counterparty).toBe("客户A");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should list transactions with filters", () => {
|
|
135
|
+
db.createTransaction({ company_id: companyId, type: "income", category: "other", amount: 1000, description: "", counterparty: "", transaction_date: "2025-01-01" });
|
|
136
|
+
db.createTransaction({ company_id: companyId, type: "expense", category: "rent", amount: 2000, description: "", counterparty: "", transaction_date: "2025-02-01" });
|
|
137
|
+
db.createTransaction({ company_id: companyId, type: "income", category: "other", amount: 3000, description: "", counterparty: "", transaction_date: "2025-03-01" });
|
|
138
|
+
|
|
139
|
+
expect(db.listTransactions(companyId).length).toBe(3);
|
|
140
|
+
expect(db.listTransactions(companyId, { type: "income" }).length).toBe(2);
|
|
141
|
+
expect(db.listTransactions(companyId, { type: "expense" }).length).toBe(1);
|
|
142
|
+
expect(db.listTransactions(companyId, { startDate: "2025-02-01" }).length).toBe(2);
|
|
143
|
+
expect(db.listTransactions(companyId, { endDate: "2025-01-31" }).length).toBe(1);
|
|
144
|
+
expect(db.listTransactions(companyId, { limit: 1 }).length).toBe(1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should return null for non-existent transaction", () => {
|
|
148
|
+
expect(db.getTransaction("fake")).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ── Finance Summary ────────────────────────────────────────
|
|
153
|
+
describe("getFinanceSummary", () => {
|
|
154
|
+
it("should compute correct financial totals", () => {
|
|
155
|
+
const c = db.createCompany({ name: "Fin Co", industry: "IT", owner_name: "X", owner_contact: "", status: "active", registered_capital: 0, description: "" });
|
|
156
|
+
db.createTransaction({ company_id: c.id, type: "income", category: "other", amount: 10000, description: "", counterparty: "", transaction_date: "2025-01-15" });
|
|
157
|
+
db.createTransaction({ company_id: c.id, type: "income", category: "other", amount: 5000, description: "", counterparty: "", transaction_date: "2025-01-20" });
|
|
158
|
+
db.createTransaction({ company_id: c.id, type: "expense", category: "rent", amount: 3000, description: "", counterparty: "", transaction_date: "2025-01-25" });
|
|
159
|
+
|
|
160
|
+
const summary = db.getFinanceSummary(c.id);
|
|
161
|
+
expect(summary.total_income).toBe(15000);
|
|
162
|
+
expect(summary.total_expense).toBe(3000);
|
|
163
|
+
expect(summary.net).toBe(12000);
|
|
164
|
+
expect(summary.count).toBe(3);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should filter by date range", () => {
|
|
168
|
+
const c = db.createCompany({ name: "Fin2", industry: "IT", owner_name: "X", owner_contact: "", status: "active", registered_capital: 0, description: "" });
|
|
169
|
+
db.createTransaction({ company_id: c.id, type: "income", category: "other", amount: 1000, description: "", counterparty: "", transaction_date: "2025-01-15" });
|
|
170
|
+
db.createTransaction({ company_id: c.id, type: "income", category: "other", amount: 2000, description: "", counterparty: "", transaction_date: "2025-03-15" });
|
|
171
|
+
|
|
172
|
+
const jan = db.getFinanceSummary(c.id, "2025-01-01", "2025-01-31");
|
|
173
|
+
expect(jan.total_income).toBe(1000);
|
|
174
|
+
|
|
175
|
+
const all = db.getFinanceSummary(c.id);
|
|
176
|
+
expect(all.total_income).toBe(3000);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should return zeros for company with no transactions", () => {
|
|
180
|
+
const c = db.createCompany({ name: "Empty", industry: "IT", owner_name: "X", owner_contact: "", status: "active", registered_capital: 0, description: "" });
|
|
181
|
+
const summary = db.getFinanceSummary(c.id);
|
|
182
|
+
expect(summary.total_income).toBe(0);
|
|
183
|
+
expect(summary.total_expense).toBe(0);
|
|
184
|
+
expect(summary.net).toBe(0);
|
|
185
|
+
expect(summary.count).toBe(0);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ── Contacts ───────────────────────────────────────────────
|
|
190
|
+
describe("Contacts CRUD", () => {
|
|
191
|
+
let companyId: string;
|
|
192
|
+
|
|
193
|
+
beforeEach(() => {
|
|
194
|
+
const c = db.createCompany({ name: "Ct Co", industry: "IT", owner_name: "X", owner_contact: "", status: "active", registered_capital: 0, description: "" });
|
|
195
|
+
companyId = c.id;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should create and retrieve a contact", () => {
|
|
199
|
+
const contact = db.createContact({
|
|
200
|
+
company_id: companyId,
|
|
201
|
+
name: "王经理",
|
|
202
|
+
phone: "13900139000",
|
|
203
|
+
email: "wang@example.com",
|
|
204
|
+
company_name: "客户公司",
|
|
205
|
+
tags: '["VIP"]',
|
|
206
|
+
notes: "重要客户",
|
|
207
|
+
last_contact_date: "2025-01-01",
|
|
208
|
+
});
|
|
209
|
+
expect(contact.id).toBeTruthy();
|
|
210
|
+
expect(contact.name).toBe("王经理");
|
|
211
|
+
|
|
212
|
+
const fetched = db.getContact(contact.id);
|
|
213
|
+
expect(fetched).not.toBeNull();
|
|
214
|
+
expect(fetched!.email).toBe("wang@example.com");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should list contacts and filter by tag", () => {
|
|
218
|
+
db.createContact({ company_id: companyId, name: "A", phone: "", email: "", company_name: "", tags: '["VIP"]', notes: "", last_contact_date: "" });
|
|
219
|
+
db.createContact({ company_id: companyId, name: "B", phone: "", email: "", company_name: "", tags: '["供应商"]', notes: "", last_contact_date: "" });
|
|
220
|
+
|
|
221
|
+
expect(db.listContacts(companyId).length).toBe(2);
|
|
222
|
+
expect(db.listContacts(companyId, "VIP").length).toBe(1);
|
|
223
|
+
expect(db.listContacts(companyId, "供应商").length).toBe(1);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("should update a contact", () => {
|
|
227
|
+
const ct = db.createContact({ company_id: companyId, name: "Old", phone: "", email: "", company_name: "", tags: "[]", notes: "", last_contact_date: "" });
|
|
228
|
+
const updated = db.updateContact(ct.id, { name: "New", phone: "12345" });
|
|
229
|
+
expect(updated).not.toBeNull();
|
|
230
|
+
expect(updated!.name).toBe("New");
|
|
231
|
+
expect(updated!.phone).toBe("12345");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("should return null when updating non-existent contact", () => {
|
|
235
|
+
expect(db.updateContact("fake", { name: "X" })).toBeNull();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("should delete a contact", () => {
|
|
239
|
+
const ct = db.createContact({ company_id: companyId, name: "Del", phone: "", email: "", company_name: "", tags: "[]", notes: "", last_contact_date: "" });
|
|
240
|
+
expect(db.deleteContact(ct.id)).toBe(true);
|
|
241
|
+
expect(db.getContact(ct.id)).toBeNull();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should return false when deleting non-existent contact", () => {
|
|
245
|
+
expect(db.deleteContact("fake")).toBe(false);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// ── Dashboard Stats ────────────────────────────────────────
|
|
250
|
+
describe("getDashboardStats", () => {
|
|
251
|
+
it("should return correct aggregate stats", () => {
|
|
252
|
+
const c1 = db.createCompany({ name: "S1", industry: "IT", owner_name: "X", owner_contact: "", status: "active", registered_capital: 0, description: "" });
|
|
253
|
+
db.createCompany({ name: "S2", industry: "IT", owner_name: "Y", owner_contact: "", status: "pending", registered_capital: 0, description: "" });
|
|
254
|
+
db.createTransaction({ company_id: c1.id, type: "income", category: "other", amount: 10000, description: "", counterparty: "", transaction_date: "2025-01-01" });
|
|
255
|
+
db.createTransaction({ company_id: c1.id, type: "expense", category: "rent", amount: 3000, description: "", counterparty: "", transaction_date: "2025-01-01" });
|
|
256
|
+
db.createContact({ company_id: c1.id, name: "Contact1", phone: "", email: "", company_name: "", tags: "[]", notes: "", last_contact_date: "" });
|
|
257
|
+
|
|
258
|
+
const stats = db.getDashboardStats();
|
|
259
|
+
expect(stats.total_companies).toBe(2);
|
|
260
|
+
expect(stats.active_companies).toBe(1);
|
|
261
|
+
expect(stats.total_transactions).toBe(2);
|
|
262
|
+
expect(stats.total_contacts).toBe(1);
|
|
263
|
+
expect(stats.total_revenue).toBe(10000);
|
|
264
|
+
expect(stats.total_expense).toBe(3000);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("should return zeros for empty database", () => {
|
|
268
|
+
const stats = db.getDashboardStats();
|
|
269
|
+
expect(stats.total_companies).toBe(0);
|
|
270
|
+
expect(stats.active_companies).toBe(0);
|
|
271
|
+
expect(stats.total_transactions).toBe(0);
|
|
272
|
+
expect(stats.total_contacts).toBe(0);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ── Generic query methods ──────────────────────────────────
|
|
277
|
+
describe("Generic query methods", () => {
|
|
278
|
+
it("genId should generate unique ids", () => {
|
|
279
|
+
const ids = new Set<string>();
|
|
280
|
+
for (let i = 0; i < 100; i++) {
|
|
281
|
+
ids.add(db.genId());
|
|
282
|
+
}
|
|
283
|
+
expect(ids.size).toBe(100);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("query should return rows", () => {
|
|
287
|
+
db.createCompany({ name: "Q1", industry: "IT", owner_name: "X", owner_contact: "", status: "active", registered_capital: 0, description: "" });
|
|
288
|
+
const rows = db.query("SELECT * FROM opc_companies WHERE status = ?", "active");
|
|
289
|
+
expect(rows.length).toBe(1);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("queryOne should return single row", () => {
|
|
293
|
+
db.createCompany({ name: "Q2", industry: "IT", owner_name: "X", owner_contact: "", status: "active", registered_capital: 0, description: "" });
|
|
294
|
+
const row = db.queryOne("SELECT COUNT(*) as cnt FROM opc_companies") as { cnt: number };
|
|
295
|
+
expect(row.cnt).toBe(1);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("execute should return changes count", () => {
|
|
299
|
+
db.createCompany({ name: "E1", industry: "IT", owner_name: "X", owner_contact: "", status: "pending", registered_capital: 0, description: "" });
|
|
300
|
+
const result = db.execute("UPDATE opc_companies SET industry = ? WHERE status = ?", "金融", "pending");
|
|
301
|
+
expect(result.changes).toBe(1);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
package/src/db/sqlite-adapter.ts
CHANGED
|
@@ -335,7 +335,7 @@ export class SqliteAdapter implements OpcDatabase {
|
|
|
335
335
|
.prepare(
|
|
336
336
|
`SELECT
|
|
337
337
|
COUNT(*) as total,
|
|
338
|
-
SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) as active
|
|
338
|
+
COALESCE(SUM(CASE WHEN status='active' THEN 1 ELSE 0 END), 0) as active
|
|
339
339
|
FROM opc_companies`,
|
|
340
340
|
)
|
|
341
341
|
.get() as { total: number; active: number };
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 星环OPC中心 — CompanyManager 单元测试
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
6
|
+
import { SqliteAdapter } from "../db/sqlite-adapter.js";
|
|
7
|
+
import { CompanyManager } from "./company-manager.js";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
|
|
12
|
+
describe("CompanyManager", () => {
|
|
13
|
+
let db: SqliteAdapter;
|
|
14
|
+
let manager: CompanyManager;
|
|
15
|
+
let dbPath: string;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
dbPath = path.join(os.tmpdir(), `opc-test-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
|
|
19
|
+
db = new SqliteAdapter(dbPath);
|
|
20
|
+
manager = new CompanyManager(db);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
db.close();
|
|
25
|
+
try { fs.unlinkSync(dbPath); } catch { /* ignore */ }
|
|
26
|
+
try { fs.unlinkSync(dbPath + "-wal"); } catch { /* ignore */ }
|
|
27
|
+
try { fs.unlinkSync(dbPath + "-shm"); } catch { /* ignore */ }
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("registerCompany", () => {
|
|
31
|
+
it("should create a company with pending status", () => {
|
|
32
|
+
const company = manager.registerCompany({
|
|
33
|
+
name: "测试科技",
|
|
34
|
+
industry: "互联网",
|
|
35
|
+
owner_name: "张三",
|
|
36
|
+
});
|
|
37
|
+
expect(company.id).toBeDefined();
|
|
38
|
+
expect(company.name).toBe("测试科技");
|
|
39
|
+
expect(company.industry).toBe("互联网");
|
|
40
|
+
expect(company.owner_name).toBe("张三");
|
|
41
|
+
expect(company.status).toBe("pending");
|
|
42
|
+
expect(company.registered_capital).toBe(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should accept optional fields", () => {
|
|
46
|
+
const company = manager.registerCompany({
|
|
47
|
+
name: "优质公司",
|
|
48
|
+
industry: "教育",
|
|
49
|
+
owner_name: "李四",
|
|
50
|
+
owner_contact: "13800138000",
|
|
51
|
+
registered_capital: 500000,
|
|
52
|
+
description: "在线教育平台",
|
|
53
|
+
});
|
|
54
|
+
expect(company.owner_contact).toBe("13800138000");
|
|
55
|
+
expect(company.registered_capital).toBe(500000);
|
|
56
|
+
expect(company.description).toBe("在线教育平台");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("getCompany", () => {
|
|
61
|
+
it("should return a company by id", () => {
|
|
62
|
+
const created = manager.registerCompany({
|
|
63
|
+
name: "查询测试",
|
|
64
|
+
industry: "零售",
|
|
65
|
+
owner_name: "王五",
|
|
66
|
+
});
|
|
67
|
+
const found = manager.getCompany(created.id);
|
|
68
|
+
expect(found).not.toBeNull();
|
|
69
|
+
expect(found!.name).toBe("查询测试");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should return null for non-existent id", () => {
|
|
73
|
+
expect(manager.getCompany("non-existent")).toBeNull();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("listCompanies", () => {
|
|
78
|
+
it("should list all companies", () => {
|
|
79
|
+
manager.registerCompany({ name: "A公司", industry: "IT", owner_name: "A" });
|
|
80
|
+
manager.registerCompany({ name: "B公司", industry: "金融", owner_name: "B" });
|
|
81
|
+
const list = manager.listCompanies();
|
|
82
|
+
expect(list.length).toBe(2);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should filter by status", () => {
|
|
86
|
+
const c = manager.registerCompany({ name: "C公司", industry: "IT", owner_name: "C" });
|
|
87
|
+
manager.registerCompany({ name: "D公司", industry: "IT", owner_name: "D" });
|
|
88
|
+
manager.activateCompany(c.id);
|
|
89
|
+
expect(manager.listCompanies("active").length).toBe(1);
|
|
90
|
+
expect(manager.listCompanies("pending").length).toBe(1);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("activateCompany", () => {
|
|
95
|
+
it("should transition pending → active", () => {
|
|
96
|
+
const c = manager.registerCompany({ name: "激活测试", industry: "IT", owner_name: "X" });
|
|
97
|
+
expect(c.status).toBe("pending");
|
|
98
|
+
const activated = manager.activateCompany(c.id);
|
|
99
|
+
expect(activated).not.toBeNull();
|
|
100
|
+
expect(activated!.status).toBe("active");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should return null for non-existent company", () => {
|
|
104
|
+
expect(manager.activateCompany("fake-id")).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("transitionStatus — valid transitions", () => {
|
|
109
|
+
it("pending → active", () => {
|
|
110
|
+
const c = manager.registerCompany({ name: "T1", industry: "IT", owner_name: "X" });
|
|
111
|
+
const result = manager.transitionStatus(c.id, "active");
|
|
112
|
+
expect(result!.status).toBe("active");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("pending → terminated", () => {
|
|
116
|
+
const c = manager.registerCompany({ name: "T2", industry: "IT", owner_name: "X" });
|
|
117
|
+
const result = manager.transitionStatus(c.id, "terminated");
|
|
118
|
+
expect(result!.status).toBe("terminated");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("active → suspended", () => {
|
|
122
|
+
const c = manager.registerCompany({ name: "T3", industry: "IT", owner_name: "X" });
|
|
123
|
+
manager.transitionStatus(c.id, "active");
|
|
124
|
+
const result = manager.transitionStatus(c.id, "suspended");
|
|
125
|
+
expect(result!.status).toBe("suspended");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("active → acquired", () => {
|
|
129
|
+
const c = manager.registerCompany({ name: "T4", industry: "IT", owner_name: "X" });
|
|
130
|
+
manager.transitionStatus(c.id, "active");
|
|
131
|
+
const result = manager.transitionStatus(c.id, "acquired");
|
|
132
|
+
expect(result!.status).toBe("acquired");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("active → packaged", () => {
|
|
136
|
+
const c = manager.registerCompany({ name: "T5", industry: "IT", owner_name: "X" });
|
|
137
|
+
manager.transitionStatus(c.id, "active");
|
|
138
|
+
const result = manager.transitionStatus(c.id, "packaged");
|
|
139
|
+
expect(result!.status).toBe("packaged");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("active → terminated", () => {
|
|
143
|
+
const c = manager.registerCompany({ name: "T6", industry: "IT", owner_name: "X" });
|
|
144
|
+
manager.transitionStatus(c.id, "active");
|
|
145
|
+
const result = manager.transitionStatus(c.id, "terminated");
|
|
146
|
+
expect(result!.status).toBe("terminated");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("suspended → active", () => {
|
|
150
|
+
const c = manager.registerCompany({ name: "T7", industry: "IT", owner_name: "X" });
|
|
151
|
+
manager.transitionStatus(c.id, "active");
|
|
152
|
+
manager.transitionStatus(c.id, "suspended");
|
|
153
|
+
const result = manager.transitionStatus(c.id, "active");
|
|
154
|
+
expect(result!.status).toBe("active");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("suspended → terminated", () => {
|
|
158
|
+
const c = manager.registerCompany({ name: "T8", industry: "IT", owner_name: "X" });
|
|
159
|
+
manager.transitionStatus(c.id, "active");
|
|
160
|
+
manager.transitionStatus(c.id, "suspended");
|
|
161
|
+
const result = manager.transitionStatus(c.id, "terminated");
|
|
162
|
+
expect(result!.status).toBe("terminated");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("acquired → terminated", () => {
|
|
166
|
+
const c = manager.registerCompany({ name: "T9", industry: "IT", owner_name: "X" });
|
|
167
|
+
manager.transitionStatus(c.id, "active");
|
|
168
|
+
manager.transitionStatus(c.id, "acquired");
|
|
169
|
+
const result = manager.transitionStatus(c.id, "terminated");
|
|
170
|
+
expect(result!.status).toBe("terminated");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("packaged → terminated", () => {
|
|
174
|
+
const c = manager.registerCompany({ name: "T10", industry: "IT", owner_name: "X" });
|
|
175
|
+
manager.transitionStatus(c.id, "active");
|
|
176
|
+
manager.transitionStatus(c.id, "packaged");
|
|
177
|
+
const result = manager.transitionStatus(c.id, "terminated");
|
|
178
|
+
expect(result!.status).toBe("terminated");
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("transitionStatus — invalid transitions", () => {
|
|
183
|
+
it("pending → suspended should throw", () => {
|
|
184
|
+
const c = manager.registerCompany({ name: "E1", industry: "IT", owner_name: "X" });
|
|
185
|
+
expect(() => manager.transitionStatus(c.id, "suspended")).toThrow();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("pending → acquired should throw", () => {
|
|
189
|
+
const c = manager.registerCompany({ name: "E2", industry: "IT", owner_name: "X" });
|
|
190
|
+
expect(() => manager.transitionStatus(c.id, "acquired")).toThrow();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("terminated → active should throw", () => {
|
|
194
|
+
const c = manager.registerCompany({ name: "E3", industry: "IT", owner_name: "X" });
|
|
195
|
+
manager.transitionStatus(c.id, "terminated");
|
|
196
|
+
expect(() => manager.transitionStatus(c.id, "active")).toThrow(/不允许/);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("acquired → active should throw", () => {
|
|
200
|
+
const c = manager.registerCompany({ name: "E4", industry: "IT", owner_name: "X" });
|
|
201
|
+
manager.transitionStatus(c.id, "active");
|
|
202
|
+
manager.transitionStatus(c.id, "acquired");
|
|
203
|
+
expect(() => manager.transitionStatus(c.id, "active")).toThrow();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("updateCompany", () => {
|
|
208
|
+
it("should update company fields", () => {
|
|
209
|
+
const c = manager.registerCompany({ name: "更新测试", industry: "IT", owner_name: "X" });
|
|
210
|
+
const updated = manager.updateCompany(c.id, { name: "新名称", industry: "金融" });
|
|
211
|
+
expect(updated).not.toBeNull();
|
|
212
|
+
expect(updated!.name).toBe("新名称");
|
|
213
|
+
expect(updated!.industry).toBe("金融");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("should return null for non-existent company", () => {
|
|
217
|
+
expect(manager.updateCompany("fake-id", { name: "test" })).toBeNull();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("deleteCompany", () => {
|
|
222
|
+
it("should delete an existing company", () => {
|
|
223
|
+
const c = manager.registerCompany({ name: "删除测试", industry: "IT", owner_name: "X" });
|
|
224
|
+
expect(manager.deleteCompany(c.id)).toBe(true);
|
|
225
|
+
expect(manager.getCompany(c.id)).toBeNull();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should return false for non-existent company", () => {
|
|
229
|
+
expect(manager.deleteCompany("fake-id")).toBe(false);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|