@oneuptime/common 12.0.3 → 12.0.4
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/Models/DatabaseModels/CephCluster.ts +14 -0
- package/Models/DatabaseModels/CodeRepository.ts +14 -6
- package/Models/DatabaseModels/DockerHost.ts +14 -0
- package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
- package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
- package/Models/DatabaseModels/IoTFleet.ts +14 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
- package/Models/DatabaseModels/NetworkDevice.ts +14 -0
- package/Models/DatabaseModels/NetworkInterface.ts +18 -0
- package/Models/DatabaseModels/Project.ts +16 -2
- package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
- package/Models/DatabaseModels/Service.ts +30 -0
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
- package/Models/DatabaseModels/User.ts +30 -0
- package/Server/API/AIAgentDataAPI.ts +31 -0
- package/Server/API/DashboardAPI.ts +419 -12
- package/Server/API/GitHubAPI.ts +119 -283
- package/Server/API/UserAPI.ts +263 -1
- package/Server/EnvironmentConfig.ts +7 -3
- package/Server/Infrastructure/GlobalCache.ts +37 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Services/AnalyticsDatabaseService.ts +72 -1
- package/Server/Services/CodeRepositoryService.ts +105 -2
- package/Server/Services/DatabaseService.ts +185 -2
- package/Server/Services/OpenTelemetryIngestService.ts +230 -26
- package/Server/Services/UserService.ts +184 -0
- package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
- package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
- package/Server/Utils/PasswordHash.ts +306 -0
- package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
- package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
- package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
- package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
- package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
- package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
- package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
- package/Tests/Server/API/Helpers.ts +6 -1
- package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +128 -0
- package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
- package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
- package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
- package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
- package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
- package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
- package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
- package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
- package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
- package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
- package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
- package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
- package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
- package/Tests/Types/Database/ColumnLength.test.ts +7 -1
- package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
- package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
- package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
- package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
- package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
- package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
- package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
- package/Types/Database/ColumnLength.ts +9 -1
- package/Types/Database/TableColumn.ts +11 -0
- package/Types/Database/UnsynchronizedIndex.ts +48 -0
- package/Types/HashedString.ts +139 -4
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
- package/UI/Components/Navbar/NavBar.tsx +12 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
- package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
- package/Utils/TeamMembersByProject.ts +237 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +16 -2
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +28 -1
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +32 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +304 -10
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/GitHubAPI.js +94 -167
- package/build/dist/Server/API/GitHubAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +187 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +7 -3
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +29 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
- package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +160 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +165 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
- package/build/dist/Server/Utils/PasswordHash.js +237 -0
- package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
- package/build/dist/Types/Database/ColumnLength.js +9 -1
- package/build/dist/Types/Database/ColumnLength.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
- package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
- package/build/dist/Types/HashedString.js +95 -5
- package/build/dist/Types/HashedString.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
- package/build/dist/Utils/TeamMembersByProject.js +145 -0
- package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { WidenHashedStringColumnsForScrypt1786023262402 } from "../../../Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt";
|
|
2
|
+
import SchemaMigrations from "../../../Server/Infrastructure/Postgres/SchemaMigrations/Index";
|
|
3
|
+
import ColumnLength from "../../../Types/Database/ColumnLength";
|
|
4
|
+
import { QueryRunner } from "typeorm";
|
|
5
|
+
import { describe, expect, test } from "@jest/globals";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* The migration that widens every HashedString column so a scrypt hash, which
|
|
9
|
+
* carries its own cost parameters, fits.
|
|
10
|
+
*
|
|
11
|
+
* THE REASON THIS FILE EXISTS.
|
|
12
|
+
*
|
|
13
|
+
* `typeorm migration:generate` does not express a varchar length change as
|
|
14
|
+
* ALTER COLUMN TYPE. It emits DROP COLUMN followed by ADD COLUMN — which for
|
|
15
|
+
* these six columns means deleting every password, every status page and
|
|
16
|
+
* dashboard master password, and every active session in the instance. The
|
|
17
|
+
* generated file was replaced by hand for exactly that reason.
|
|
18
|
+
*
|
|
19
|
+
* Anyone who regenerates this migration (or writes the next widening the same
|
|
20
|
+
* way) gets the destructive version back, and it looks perfectly ordinary in
|
|
21
|
+
* review. The first test below is the guard: no statement in this migration
|
|
22
|
+
* may drop a column.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
type MakeQueryRunnerResult = {
|
|
26
|
+
runner: QueryRunner;
|
|
27
|
+
statements: Array<string>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type MakeQueryRunnerFunction = () => MakeQueryRunnerResult;
|
|
31
|
+
|
|
32
|
+
const makeQueryRunner: MakeQueryRunnerFunction = (): MakeQueryRunnerResult => {
|
|
33
|
+
const statements: Array<string> = [];
|
|
34
|
+
|
|
35
|
+
const query: (...args: Array<unknown>) => Promise<undefined> = (
|
|
36
|
+
...args: Array<unknown>
|
|
37
|
+
): Promise<undefined> => {
|
|
38
|
+
statements.push(String(args[0]));
|
|
39
|
+
return Promise.resolve(undefined);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
runner: { query } as unknown as QueryRunner,
|
|
44
|
+
statements,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const WIDENED: ReadonlyArray<[string, string]> = [
|
|
49
|
+
["User", "password"],
|
|
50
|
+
["StatusPagePrivateUser", "password"],
|
|
51
|
+
["StatusPage", "masterPassword"],
|
|
52
|
+
["Dashboard", "masterPassword"],
|
|
53
|
+
["UserSession", "refreshToken"],
|
|
54
|
+
["StatusPagePrivateUserSession", "refreshToken"],
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
describe("WidenHashedStringColumnsForScrypt — must not destroy data", () => {
|
|
58
|
+
test("up() never drops a column", async () => {
|
|
59
|
+
/*
|
|
60
|
+
* The whole point. A DROP COLUMN here silently deletes every credential
|
|
61
|
+
* in the database, and the migration would still 'succeed'.
|
|
62
|
+
*/
|
|
63
|
+
const { runner, statements } = makeQueryRunner();
|
|
64
|
+
|
|
65
|
+
await new WidenHashedStringColumnsForScrypt1786023262402().up(runner);
|
|
66
|
+
|
|
67
|
+
for (const statement of statements) {
|
|
68
|
+
expect(statement).not.toMatch(/DROP COLUMN/i);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("down() never drops a column either", async () => {
|
|
73
|
+
const { runner, statements } = makeQueryRunner();
|
|
74
|
+
|
|
75
|
+
await new WidenHashedStringColumnsForScrypt1786023262402().down(runner);
|
|
76
|
+
|
|
77
|
+
for (const statement of statements) {
|
|
78
|
+
expect(statement).not.toMatch(/DROP COLUMN/i);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("up() is nothing but ALTER COLUMN TYPE statements", async () => {
|
|
83
|
+
const { runner, statements } = makeQueryRunner();
|
|
84
|
+
|
|
85
|
+
await new WidenHashedStringColumnsForScrypt1786023262402().up(runner);
|
|
86
|
+
|
|
87
|
+
for (const statement of statements) {
|
|
88
|
+
expect(statement).toMatch(
|
|
89
|
+
/^ALTER TABLE "[A-Za-z]+" ALTER COLUMN "[A-Za-z]+" TYPE character varying\(\d+\)$/,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("it does not touch constraints or indexes", async () => {
|
|
95
|
+
/*
|
|
96
|
+
* The generated version dropped and recreated the refresh-token unique
|
|
97
|
+
* constraints and indexes purely to work around its own DROP COLUMN.
|
|
98
|
+
* Widening a varchar needs no table rewrite, so the indexes survive
|
|
99
|
+
* untouched and none of that churn is necessary.
|
|
100
|
+
*/
|
|
101
|
+
const { runner, statements } = makeQueryRunner();
|
|
102
|
+
|
|
103
|
+
await new WidenHashedStringColumnsForScrypt1786023262402().up(runner);
|
|
104
|
+
|
|
105
|
+
for (const statement of statements) {
|
|
106
|
+
expect(statement).not.toMatch(/CONSTRAINT/i);
|
|
107
|
+
expect(statement).not.toMatch(/INDEX/i);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("WidenHashedStringColumnsForScrypt — SQL contract", () => {
|
|
113
|
+
test("up() widens exactly the six HashedString columns", async () => {
|
|
114
|
+
const { runner, statements } = makeQueryRunner();
|
|
115
|
+
|
|
116
|
+
await new WidenHashedStringColumnsForScrypt1786023262402().up(runner);
|
|
117
|
+
|
|
118
|
+
expect(statements).toHaveLength(WIDENED.length);
|
|
119
|
+
|
|
120
|
+
for (const [table, column] of WIDENED) {
|
|
121
|
+
expect(statements).toContain(
|
|
122
|
+
`ALTER TABLE "${table}" ALTER COLUMN "${column}" TYPE character varying(255)`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("it widens to whatever ColumnLength.HashedString says", async () => {
|
|
128
|
+
/*
|
|
129
|
+
* The entity metadata and the migration have to agree, or the drift check
|
|
130
|
+
* fails and the next generated migration tries to 'fix' it.
|
|
131
|
+
*/
|
|
132
|
+
const { runner, statements } = makeQueryRunner();
|
|
133
|
+
|
|
134
|
+
await new WidenHashedStringColumnsForScrypt1786023262402().up(runner);
|
|
135
|
+
|
|
136
|
+
for (const statement of statements) {
|
|
137
|
+
expect(statement).toContain(
|
|
138
|
+
`character varying(${ColumnLength.HashedString})`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("down() returns the same six columns to 64", async () => {
|
|
144
|
+
const { runner, statements } = makeQueryRunner();
|
|
145
|
+
|
|
146
|
+
await new WidenHashedStringColumnsForScrypt1786023262402().down(runner);
|
|
147
|
+
|
|
148
|
+
expect(statements).toHaveLength(WIDENED.length);
|
|
149
|
+
|
|
150
|
+
for (const [table, column] of WIDENED) {
|
|
151
|
+
expect(statements).toContain(
|
|
152
|
+
`ALTER TABLE "${table}" ALTER COLUMN "${column}" TYPE character varying(64)`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("down() reverses up()'s order", async () => {
|
|
158
|
+
const up: MakeQueryRunnerResult = makeQueryRunner();
|
|
159
|
+
const down: MakeQueryRunnerResult = makeQueryRunner();
|
|
160
|
+
|
|
161
|
+
await new WidenHashedStringColumnsForScrypt1786023262402().up(up.runner);
|
|
162
|
+
await new WidenHashedStringColumnsForScrypt1786023262402().down(
|
|
163
|
+
down.runner,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
type ColumnsFunction = (statements: Array<string>) => Array<string>;
|
|
167
|
+
|
|
168
|
+
const columns: ColumnsFunction = (
|
|
169
|
+
statements: Array<string>,
|
|
170
|
+
): Array<string> => {
|
|
171
|
+
return statements.map((statement: string) => {
|
|
172
|
+
return statement
|
|
173
|
+
.match(/ALTER TABLE "([^"]+)" ALTER COLUMN "([^"]+)"/)!
|
|
174
|
+
.slice(1, 3)
|
|
175
|
+
.join(".");
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
expect(columns(down.statements)).toEqual(columns(up.statements).reverse());
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("the widened column holds a scrypt hash with room to spare", () => {
|
|
183
|
+
// `scrypt$N=16384,r=8,p=1$` + 64 hex characters.
|
|
184
|
+
const worstCaseToday: number = "scrypt$N=1048576,r=32,p=16$".length + 64;
|
|
185
|
+
|
|
186
|
+
expect(ColumnLength.HashedString).toBeGreaterThan(worstCaseToday);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("WidenHashedStringColumnsForScrypt — registration", () => {
|
|
191
|
+
test("is registered in SchemaMigrations/Index.ts", () => {
|
|
192
|
+
expect(SchemaMigrations).toContain(
|
|
193
|
+
WidenHashedStringColumnsForScrypt1786023262402,
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("appears exactly once", () => {
|
|
198
|
+
const occurrences: number = SchemaMigrations.filter(
|
|
199
|
+
(migration: { name: string }) => {
|
|
200
|
+
return (
|
|
201
|
+
migration.name === "WidenHashedStringColumnsForScrypt1786023262402"
|
|
202
|
+
);
|
|
203
|
+
},
|
|
204
|
+
).length;
|
|
205
|
+
|
|
206
|
+
expect(occurrences).toBe(1);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("runs after the migration that added the salt columns", () => {
|
|
210
|
+
const names: Array<string> = SchemaMigrations.map(
|
|
211
|
+
(migration: { name: string }) => {
|
|
212
|
+
return migration.name;
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
expect(
|
|
217
|
+
names.indexOf("WidenHashedStringColumnsForScrypt1786023262402"),
|
|
218
|
+
).toBeGreaterThan(names.indexOf("AddPerUserPasswordSalt1786018109307"));
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -15,7 +15,8 @@ import CodeRepositoryModel from "../../../../Models/DatabaseModels/CodeRepositor
|
|
|
15
15
|
import TelemetryException from "../../../../Models/DatabaseModels/TelemetryException";
|
|
16
16
|
import CodeRepositoryType from "../../../../Types/CodeRepository/CodeRepositoryType";
|
|
17
17
|
import ObjectID from "../../../../Types/ObjectID";
|
|
18
|
-
import
|
|
18
|
+
import GitHubInstallationBinding from "../../../../Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding";
|
|
19
|
+
import { describe, expect, test, afterEach, beforeEach } from "@jest/globals";
|
|
19
20
|
|
|
20
21
|
/*
|
|
21
22
|
* The code tools are the bridge from a telemetry signal to the source that
|
|
@@ -86,6 +87,19 @@ function mockFileContent(content: string): void {
|
|
|
86
87
|
});
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/*
|
|
91
|
+
* findReadableRepositories now also proves each row's GitHub App installation
|
|
92
|
+
* belongs to the row's project (GHSA-xx95-gmcf-7q86), which is a database
|
|
93
|
+
* read. That guard has its own tests; here it would just be a Postgres
|
|
94
|
+
* connection these tool tests do not need, so treat every fixture row as
|
|
95
|
+
* bound.
|
|
96
|
+
*/
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
jest
|
|
99
|
+
.spyOn(GitHubInstallationBinding, "getBoundInstallationId")
|
|
100
|
+
.mockResolvedValue("12345");
|
|
101
|
+
});
|
|
102
|
+
|
|
89
103
|
afterEach(() => {
|
|
90
104
|
jest.restoreAllMocks();
|
|
91
105
|
});
|
|
@@ -119,6 +133,38 @@ describe("list_code_repositories", () => {
|
|
|
119
133
|
expect(result.rowCount).toBe(1);
|
|
120
134
|
});
|
|
121
135
|
|
|
136
|
+
/*
|
|
137
|
+
* GHSA-xx95-gmcf-7q86. A row can be in this project and still name a GitHub
|
|
138
|
+
* App installation the project does not own — that combination used to be
|
|
139
|
+
* creatable, and these tools would then read, commit and open pull requests
|
|
140
|
+
* in another tenant's private repositories. Being in the project is not the
|
|
141
|
+
* same as owning the installation, so an unbound row must be invisible here
|
|
142
|
+
* exactly like an uninstalled one.
|
|
143
|
+
*/
|
|
144
|
+
test("hides a repository whose installation is not bound to the project", async () => {
|
|
145
|
+
jest
|
|
146
|
+
.spyOn(GitHubInstallationBinding, "getBoundInstallationId")
|
|
147
|
+
.mockResolvedValue("12345");
|
|
148
|
+
|
|
149
|
+
mockRepositories([
|
|
150
|
+
buildRepository({ name: "our-repo", installationId: "12345" }),
|
|
151
|
+
buildRepository({
|
|
152
|
+
id: ObjectID.generate(),
|
|
153
|
+
name: "someone-elses-repo",
|
|
154
|
+
installationId: "99999999",
|
|
155
|
+
}),
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
const result: ToolExecutionResult = await ListCodeRepositoriesTool.execute(
|
|
159
|
+
{},
|
|
160
|
+
ctx,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(result.dataForLlm).toContain("our-repo");
|
|
164
|
+
expect(result.dataForLlm).not.toContain("someone-elses-repo");
|
|
165
|
+
expect(result.rowCount).toBe(1);
|
|
166
|
+
});
|
|
167
|
+
|
|
122
168
|
test("says plainly when no repository is connected", async () => {
|
|
123
169
|
mockRepositories([]);
|
|
124
170
|
|
|
@@ -23,6 +23,7 @@ import AIAgentTaskPullRequest from "../../../../Models/DatabaseModels/AIAgentTas
|
|
|
23
23
|
import CodeRepositoryType from "../../../../Types/CodeRepository/CodeRepositoryType";
|
|
24
24
|
import PullRequestState from "../../../../Types/CodeRepository/PullRequestState";
|
|
25
25
|
import ObjectID from "../../../../Types/ObjectID";
|
|
26
|
+
import GitHubInstallationBinding from "../../../../Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding";
|
|
26
27
|
import { describe, expect, test, afterEach, beforeEach } from "@jest/globals";
|
|
27
28
|
|
|
28
29
|
/*
|
|
@@ -68,6 +69,15 @@ beforeEach(() => {
|
|
|
68
69
|
.spyOn(CodeRepositoryService, "findBy")
|
|
69
70
|
.mockResolvedValue([buildRepository()] as never);
|
|
70
71
|
jest.spyOn(GitHubUtil, "getDefaultBranchName").mockResolvedValue("main");
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* resolveTargetRepository now proves each row's GitHub App installation
|
|
75
|
+
* belongs to the row's project (GHSA-xx95-gmcf-7q86), which is a database
|
|
76
|
+
* read. That guard has its own tests; treat the fixture row as bound.
|
|
77
|
+
*/
|
|
78
|
+
jest
|
|
79
|
+
.spyOn(GitHubInstallationBinding, "getBoundInstallationId")
|
|
80
|
+
.mockResolvedValue("12345");
|
|
71
81
|
});
|
|
72
82
|
|
|
73
83
|
afterEach(() => {
|
|
@@ -24,6 +24,7 @@ import IncludesNone from "../../../../Types/BaseDatabase/IncludesNone";
|
|
|
24
24
|
import MultiSearch from "../../../../Types/BaseDatabase/MultiSearch";
|
|
25
25
|
import Search from "../../../../Types/BaseDatabase/Search";
|
|
26
26
|
import StartsWith from "../../../../Types/BaseDatabase/StartsWith";
|
|
27
|
+
import SortOrder from "../../../../Types/BaseDatabase/SortOrder";
|
|
27
28
|
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
28
29
|
|
|
29
30
|
function expectStatement(actual: Statement, expected: Statement): void {
|
|
@@ -1431,6 +1432,125 @@ describe("StatementGenerator", () => {
|
|
|
1431
1432
|
});
|
|
1432
1433
|
});
|
|
1433
1434
|
|
|
1435
|
+
/*
|
|
1436
|
+
* The separator here is load-bearing for pagination. `toSortStatement`
|
|
1437
|
+
* used to append sort keys with no delimiter, so anything past the first
|
|
1438
|
+
* key produced a malformed term (`col_1 DESCcol_2 ASC`) — which meant a
|
|
1439
|
+
* multi-key ORDER BY was unusable and, in practice, every paginated read
|
|
1440
|
+
* ran on a single, non-unique sort column. See the pagination-stability
|
|
1441
|
+
* suite in Tests/Server/Services/AnalyticsDatabasePaginationStability.
|
|
1442
|
+
*/
|
|
1443
|
+
describe("toSortStatement", () => {
|
|
1444
|
+
/*
|
|
1445
|
+
* TestModel declares its columns through `tableColumns`, not as TS
|
|
1446
|
+
* properties, so a `Sort<TestModel>` literal would fail excess-property
|
|
1447
|
+
* checking on every key. Funnelling through one helper keeps the cast
|
|
1448
|
+
* in a single place instead of on each call.
|
|
1449
|
+
*/
|
|
1450
|
+
const sortStatementFor: (sort: Record<string, SortOrder>) => Statement = (
|
|
1451
|
+
sort: Record<string, SortOrder>,
|
|
1452
|
+
): Statement => {
|
|
1453
|
+
return generator.toSortStatement(sort as any);
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
test("renders a single ascending key with no trailing separator", () => {
|
|
1457
|
+
const statement: Statement = sortStatementFor({
|
|
1458
|
+
column_1: SortOrder.Ascending,
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
expect(statement.query).toBe("{p0:Identifier} ASC");
|
|
1462
|
+
expect(statement.query_params).toStrictEqual({ p0: "column_1" });
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
test("renders a single descending key", () => {
|
|
1466
|
+
const statement: Statement = sortStatementFor({
|
|
1467
|
+
column_1: SortOrder.Descending,
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
expect(statement.query).toBe("{p0:Identifier} DESC");
|
|
1471
|
+
expect(statement.query_params).toStrictEqual({ p0: "column_1" });
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
test("comma separates two keys", () => {
|
|
1475
|
+
const statement: Statement = sortStatementFor({
|
|
1476
|
+
column_1: SortOrder.Descending,
|
|
1477
|
+
column_2: SortOrder.Ascending,
|
|
1478
|
+
});
|
|
1479
|
+
|
|
1480
|
+
expect(statement.query).toBe("{p0:Identifier} DESC, {p1:Identifier} ASC");
|
|
1481
|
+
expect(statement.query_params).toStrictEqual({
|
|
1482
|
+
p0: "column_1",
|
|
1483
|
+
p1: "column_2",
|
|
1484
|
+
});
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
test("comma separates three keys and preserves declaration order", () => {
|
|
1488
|
+
const statement: Statement = sortStatementFor({
|
|
1489
|
+
column_2: SortOrder.Ascending,
|
|
1490
|
+
column_1: SortOrder.Descending,
|
|
1491
|
+
column_ObjectID: SortOrder.Ascending,
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
expect(statement.query).toBe(
|
|
1495
|
+
"{p0:Identifier} ASC, {p1:Identifier} DESC, {p2:Identifier} ASC",
|
|
1496
|
+
);
|
|
1497
|
+
expect(statement.query_params).toStrictEqual({
|
|
1498
|
+
p0: "column_2",
|
|
1499
|
+
p1: "column_1",
|
|
1500
|
+
p2: "column_ObjectID",
|
|
1501
|
+
});
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
/*
|
|
1505
|
+
* Regression guard for the exact malformed shape. Asserting on the
|
|
1506
|
+
* rendered text rather than the param map is deliberate: the old bug
|
|
1507
|
+
* produced correct PARAMS and broken SQL, so a params-only assertion
|
|
1508
|
+
* would have passed straight through it.
|
|
1509
|
+
*/
|
|
1510
|
+
test("never concatenates a direction into the next identifier", () => {
|
|
1511
|
+
const statement: Statement = sortStatementFor({
|
|
1512
|
+
column_1: SortOrder.Descending,
|
|
1513
|
+
column_2: SortOrder.Ascending,
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
expect(statement.query).not.toContain("DESC{");
|
|
1517
|
+
expect(statement.query).not.toContain("ASC{");
|
|
1518
|
+
expect(statement.query).toContain(", ");
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
test("separator count is always one fewer than the key count", () => {
|
|
1522
|
+
const statement: Statement = sortStatementFor({
|
|
1523
|
+
column_ObjectID: SortOrder.Ascending,
|
|
1524
|
+
column_1: SortOrder.Ascending,
|
|
1525
|
+
column_2: SortOrder.Ascending,
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
expect(statement.query.split(", ")).toHaveLength(3);
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1531
|
+
test("an empty sort produces an empty statement", () => {
|
|
1532
|
+
const statement: Statement = sortStatementFor({});
|
|
1533
|
+
|
|
1534
|
+
expect(statement.query).toBe("");
|
|
1535
|
+
expect(statement.query_params).toStrictEqual({});
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
test("still rejects an unknown column", () => {
|
|
1539
|
+
expect(() => {
|
|
1540
|
+
return sortStatementFor({ not_a_column: SortOrder.Ascending });
|
|
1541
|
+
}).toThrow(BadDataException);
|
|
1542
|
+
});
|
|
1543
|
+
|
|
1544
|
+
test("rejects an unknown column that appears after a valid one", () => {
|
|
1545
|
+
expect(() => {
|
|
1546
|
+
return sortStatementFor({
|
|
1547
|
+
column_1: SortOrder.Ascending,
|
|
1548
|
+
not_a_column: SortOrder.Descending,
|
|
1549
|
+
});
|
|
1550
|
+
}).toThrow(BadDataException);
|
|
1551
|
+
});
|
|
1552
|
+
});
|
|
1553
|
+
|
|
1434
1554
|
describe("toColumnsCreateStatement", () => {
|
|
1435
1555
|
test("should return the columns of a CREATE TABLE statement", () => {
|
|
1436
1556
|
const statement: Statement = generator.toColumnsCreateStatement([
|