@oneuptime/common 12.0.29 → 12.0.30

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.
Files changed (189) hide show
  1. package/Models/DatabaseModels/Index.ts +11 -0
  2. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +43 -0
  3. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +662 -0
  4. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +584 -0
  5. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +546 -0
  6. package/Models/DatabaseModels/UserOnCallShiftReminder.ts +231 -0
  7. package/Models/DatabaseModels/UserOnCallShiftReminderLog.ts +331 -0
  8. package/Server/API/OnCallCalendarAPI.ts +2194 -0
  9. package/Server/EnvironmentConfig.ts +138 -0
  10. package/Server/Infrastructure/OnCallCalendarFeedCache.ts +1050 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.ts +253 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  13. package/Server/Middleware/OnCallCalendarFeedRateLimit.ts +487 -0
  14. package/Server/Services/Index.ts +12 -0
  15. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +32 -0
  16. package/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.ts +260 -0
  17. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +70 -3
  18. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +138 -3
  19. package/Server/Services/OnCallDutyPolicyScheduleService.ts +1465 -134
  20. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +167 -31
  21. package/Server/Services/ProjectOnCallCalendarFeedService.ts +192 -0
  22. package/Server/Services/TeamMemberService.ts +489 -1
  23. package/Server/Services/TeamService.ts +50 -0
  24. package/Server/Services/UserNotificationSettingService.ts +44 -1
  25. package/Server/Services/UserOnCallCalendarFeedService.ts +159 -0
  26. package/Server/Services/UserOnCallShiftReminderLogService.ts +151 -0
  27. package/Server/Services/UserOnCallShiftReminderService.ts +112 -0
  28. package/Server/Utils/OnCall/CalendarFeedToken.ts +241 -0
  29. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1488 -0
  30. package/Server/Utils/OnCall/OnCallCalendarFeedUrls.ts +230 -0
  31. package/Server/Utils/OnCall/OnCallShiftChangeListeners.ts +227 -0
  32. package/Server/Utils/OnCall/OnCallShiftMaterializer.ts +1127 -0
  33. package/Server/Utils/OnCall/OnCallShiftReminderListener.ts +174 -0
  34. package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +2584 -0
  35. package/Server/Utils/Response.ts +134 -0
  36. package/Server/Utils/StartServer.ts +10 -0
  37. package/Server/Utils/Telemetry/AppMetrics.ts +51 -0
  38. package/Tests/App/Dashboard/OnCallCalendarFeedLinks.test.tsx +229 -0
  39. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +489 -0
  40. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +149 -0
  41. package/Tests/App/Dashboard/OnCallCalendarFeedUtil.test.ts +855 -0
  42. package/Tests/App/Dashboard/OnCallPersonalCalendarFeed.test.tsx +1209 -0
  43. package/Tests/App/Dashboard/OnCallSharedCalendarFeedCard.test.tsx +1019 -0
  44. package/Tests/App/Dashboard/OnCallShiftRemindersAndUpcomingShifts.test.tsx +655 -0
  45. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +73 -0
  46. package/Tests/Models/OnCallCalendarFeedModels.test.ts +642 -0
  47. package/Tests/Models/OnCallDutyPolicyScheduleCalendarFeed.test.ts +288 -0
  48. package/Tests/Models/OnCallDutyPolicyScheduleShiftConfigVersion.test.ts +88 -0
  49. package/Tests/Models/UserOnCallShiftReminderModels.test.ts +368 -0
  50. package/Tests/Server/API/Helpers.ts +8 -0
  51. package/Tests/Server/API/OnCallCalendarAPI.test.ts +4461 -0
  52. package/Tests/Server/EnvironmentConfigOnCallCalendarFeed.test.ts +357 -0
  53. package/Tests/Server/Infrastructure/OnCallCalendarFeedCache.test.ts +1589 -0
  54. package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +369 -0
  55. package/Tests/Server/Middleware/OnCallCalendarFeedRateLimit.test.ts +1365 -0
  56. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +70 -0
  57. package/Tests/Server/Services/OnCallCalendarFeedServices.test.ts +1103 -0
  58. package/Tests/Server/Services/OnCallDutyPolicyScheduleResolver.test.ts +1037 -0
  59. package/Tests/Server/Services/OnCallShiftConfigPropagation.test.ts +1270 -0
  60. package/Tests/Server/Services/TeamDeleteMemberCleanup.test.ts +156 -0
  61. package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +755 -0
  62. package/Tests/Server/Services/UserNotificationSettingShiftReminderDefaults.test.ts +267 -0
  63. package/Tests/Server/Services/UserOnCallShiftReminderServices.test.ts +583 -0
  64. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +254 -0
  65. package/Tests/Server/Utils/OnCall/CalendarFeedToken.test.ts +467 -0
  66. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedRenderer.test.ts +2142 -0
  67. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedUrls.test.ts +278 -0
  68. package/Tests/Server/Utils/OnCall/OnCallResolverTestHarness.ts +440 -0
  69. package/Tests/Server/Utils/OnCall/OnCallShiftChangeListeners.test.ts +314 -0
  70. package/Tests/Server/Utils/OnCall/OnCallShiftMaterializer.test.ts +1502 -0
  71. package/Tests/Server/Utils/OnCall/OnCallShiftReminderListener.test.ts +491 -0
  72. package/Tests/Server/Utils/OnCall/OnCallShiftReminderRunner.test.ts +3088 -0
  73. package/Tests/Server/Utils/OnCall/OnCallShiftReminderTestHarness.ts +910 -0
  74. package/Tests/Server/Utils/ResponseSendCalendarResponse.test.ts +561 -0
  75. package/Tests/Server/Utils/StartServerEncryptionSecretWarning.test.ts +64 -0
  76. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +29 -0
  77. package/Tests/Types/Calendar/ICalendar.test.ts +488 -0
  78. package/Tests/Types/NotificationSetting/NotificationSettingEventType.test.ts +53 -0
  79. package/Tests/Types/OnCallDutyPolicy/CalendarFeedTestFixtures.ts +310 -0
  80. package/Tests/Types/OnCallDutyPolicy/CalendarFeedWindow.test.ts +205 -0
  81. package/Tests/Types/OnCallDutyPolicy/LayerUtilLayerMetaAndIterationCap.test.ts +458 -0
  82. package/Tests/Types/OnCallDutyPolicy/MaterializedShift.test.ts +197 -0
  83. package/Tests/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.test.ts +1986 -0
  84. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtilGroupKey.test.ts +678 -0
  85. package/Tests/Types/OnCallDutyPolicy/ShiftSeamUtil.test.ts +417 -0
  86. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  87. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  88. package/Types/Calendar/ICalendar.ts +442 -0
  89. package/Types/Email/EmailTemplateType.ts +2 -0
  90. package/Types/NotificationSetting/NotificationSettingEventType.ts +4 -0
  91. package/Types/OnCallDutyPolicy/CalendarFeedWindow.ts +140 -0
  92. package/Types/OnCallDutyPolicy/Layer.ts +190 -35
  93. package/Types/OnCallDutyPolicy/MaterializedShift.ts +274 -0
  94. package/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.ts +1415 -0
  95. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +159 -4
  96. package/Types/OnCallDutyPolicy/ShiftSeamUtil.ts +106 -0
  97. package/build/dist/Models/DatabaseModels/Index.js +11 -0
  98. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  99. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +44 -0
  100. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  101. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +699 -0
  102. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -0
  103. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +620 -0
  104. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -0
  105. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +585 -0
  106. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -0
  107. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js +247 -0
  108. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js.map +1 -0
  109. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js +355 -0
  110. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js.map +1 -0
  111. package/build/dist/Server/API/OnCallCalendarAPI.js +1371 -0
  112. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -0
  113. package/build/dist/Server/EnvironmentConfig.js +102 -0
  114. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  115. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js +663 -0
  116. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js.map +1 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js +103 -0
  118. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js.map +1 -0
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  121. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js +342 -0
  122. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js.map +1 -0
  123. package/build/dist/Server/Services/Index.js +12 -0
  124. package/build/dist/Server/Services/Index.js.map +1 -1
  125. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +20 -0
  126. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  127. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js +219 -0
  128. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js.map +1 -0
  129. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +47 -3
  130. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  131. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +112 -3
  132. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  133. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +955 -9
  134. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  135. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +93 -6
  136. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  137. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js +157 -0
  138. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js.map +1 -0
  139. package/build/dist/Server/Services/TeamMemberService.js +396 -1
  140. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  141. package/build/dist/Server/Services/TeamService.js +43 -0
  142. package/build/dist/Server/Services/TeamService.js.map +1 -1
  143. package/build/dist/Server/Services/UserNotificationSettingService.js +26 -1
  144. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  145. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js +140 -0
  146. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js.map +1 -0
  147. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js +116 -0
  148. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js.map +1 -0
  149. package/build/dist/Server/Services/UserOnCallShiftReminderService.js +92 -0
  150. package/build/dist/Server/Services/UserOnCallShiftReminderService.js.map +1 -0
  151. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js +161 -0
  152. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js.map +1 -0
  153. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js +983 -0
  154. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js.map +1 -0
  155. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js +155 -0
  156. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js.map +1 -0
  157. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js +145 -0
  158. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js.map +1 -0
  159. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js +752 -0
  160. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js.map +1 -0
  161. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js +92 -0
  162. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js.map +1 -0
  163. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1786 -0
  164. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -0
  165. package/build/dist/Server/Utils/Response.js +97 -0
  166. package/build/dist/Server/Utils/Response.js.map +1 -1
  167. package/build/dist/Server/Utils/StartServer.js +9 -1
  168. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  169. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +41 -0
  170. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  171. package/build/dist/Types/Calendar/ICalendar.js +290 -0
  172. package/build/dist/Types/Calendar/ICalendar.js.map +1 -0
  173. package/build/dist/Types/Email/EmailTemplateType.js +2 -0
  174. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  175. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
  176. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  177. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js +97 -0
  178. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js.map +1 -0
  179. package/build/dist/Types/OnCallDutyPolicy/Layer.js +113 -19
  180. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  181. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js +134 -0
  182. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js.map +1 -0
  183. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js +800 -0
  184. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js.map +1 -0
  185. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +97 -3
  186. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  187. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js +77 -0
  188. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js.map +1 -0
  189. package/package.json +1 -1
@@ -0,0 +1,288 @@
1
+ import AllModelTypes from "../../Models/DatabaseModels/Index";
2
+ import BaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
3
+ import OnCallDutyPolicySchedule from "../../Models/DatabaseModels/OnCallDutyPolicySchedule";
4
+ import OnCallDutyPolicyScheduleCalendarFeed from "../../Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed";
5
+ import ProjectOnCallCalendarFeed from "../../Models/DatabaseModels/ProjectOnCallCalendarFeed";
6
+ import { PlanType } from "../../Types/Billing/SubscriptionPlan";
7
+ import { ColumnAccessControl } from "../../Types/BaseDatabase/AccessControl";
8
+ import TableColumnType from "../../Types/Database/TableColumnType";
9
+ import Permission from "../../Types/Permission";
10
+ import { describe, expect, test } from "@jest/globals";
11
+
12
+ /*
13
+ * The shared schedule feed is a project capability that renders one
14
+ * schedule's whole roster to whoever holds the link. Who may publish, rotate
15
+ * or copy it therefore has to be EXACTLY who may edit or read the schedule -
16
+ * not a hand-typed approximation that drifts the next time somebody adds a
17
+ * role tier to OnCallDutyPolicySchedule. This pins the four access lists to
18
+ * the schedule model's, operation by operation, and the same for the
19
+ * project-wide feed, which follows the schedule model rather than any of its
20
+ * own.
21
+ */
22
+
23
+ type ModelType = { new (): BaseModel };
24
+
25
+ const schedule: OnCallDutyPolicySchedule = new OnCallDutyPolicySchedule();
26
+ const scheduleFeed: OnCallDutyPolicyScheduleCalendarFeed =
27
+ new OnCallDutyPolicyScheduleCalendarFeed();
28
+ const projectFeed: ProjectOnCallCalendarFeed = new ProjectOnCallCalendarFeed();
29
+
30
+ const SHARED_FEEDS: Array<[string, BaseModel]> = [
31
+ ["OnCallDutyPolicyScheduleCalendarFeed", scheduleFeed],
32
+ ["ProjectOnCallCalendarFeed", projectFeed],
33
+ ];
34
+
35
+ describe("OnCallDutyPolicyScheduleCalendarFeed access control", () => {
36
+ test("the schedule model really carries four non-empty lists to copy", () => {
37
+ // Guards the equality assertions against passing on two empty lists.
38
+ expect(schedule.createRecordPermissions.length).toBeGreaterThan(0);
39
+ expect(schedule.readRecordPermissions.length).toBeGreaterThan(0);
40
+ expect(schedule.updateRecordPermissions.length).toBeGreaterThan(0);
41
+ expect(schedule.deleteRecordPermissions.length).toBeGreaterThan(0);
42
+ });
43
+
44
+ test.each(SHARED_FEEDS)(
45
+ "%s create list deep-equals the schedule's",
46
+ (_name: string, model: BaseModel) => {
47
+ expect(model.createRecordPermissions).toEqual(
48
+ schedule.createRecordPermissions,
49
+ );
50
+ },
51
+ );
52
+
53
+ test.each(SHARED_FEEDS)(
54
+ "%s read list deep-equals the schedule's",
55
+ (_name: string, model: BaseModel) => {
56
+ expect(model.readRecordPermissions).toEqual(
57
+ schedule.readRecordPermissions,
58
+ );
59
+ },
60
+ );
61
+
62
+ test.each(SHARED_FEEDS)(
63
+ "%s update list deep-equals the schedule's",
64
+ (_name: string, model: BaseModel) => {
65
+ expect(model.updateRecordPermissions).toEqual(
66
+ schedule.updateRecordPermissions,
67
+ );
68
+ },
69
+ );
70
+
71
+ test.each(SHARED_FEEDS)(
72
+ "%s delete list deep-equals the schedule's",
73
+ (_name: string, model: BaseModel) => {
74
+ expect(model.deleteRecordPermissions).toEqual(
75
+ schedule.deleteRecordPermissions,
76
+ );
77
+ },
78
+ );
79
+
80
+ test("the copied lists are the verbatim ones from the schedule model, in order", () => {
81
+ /*
82
+ * Written out so a reviewer can see the contract without opening the
83
+ * schedule model: editors publish/rotate, readers (incl. the viewer
84
+ * tiers) copy the link. If OnCallDutyPolicySchedule gains a tier this
85
+ * fails alongside the deep-equal tests above and tells you what changed.
86
+ */
87
+ expect(scheduleFeed.createRecordPermissions).toEqual([
88
+ Permission.ProjectOwner,
89
+ Permission.ProjectAdmin,
90
+ Permission.ProjectMember,
91
+ Permission.OnCallAdmin,
92
+ Permission.OnCallMember,
93
+ Permission.CreateProjectOnCallDutyPolicySchedule,
94
+ ]);
95
+ expect(scheduleFeed.readRecordPermissions).toEqual([
96
+ Permission.ProjectOwner,
97
+ Permission.ProjectAdmin,
98
+ Permission.ProjectMember,
99
+ Permission.Viewer,
100
+ Permission.OnCallAdmin,
101
+ Permission.OnCallMember,
102
+ Permission.OnCallViewer,
103
+ Permission.ReadProjectOnCallDutyPolicySchedule,
104
+ ]);
105
+ expect(scheduleFeed.deleteRecordPermissions).toEqual([
106
+ Permission.ProjectOwner,
107
+ Permission.ProjectAdmin,
108
+ Permission.ProjectMember,
109
+ Permission.OnCallAdmin,
110
+ Permission.OnCallMember,
111
+ Permission.DeleteProjectOnCallDutyPolicySchedule,
112
+ ]);
113
+ expect(scheduleFeed.updateRecordPermissions).toEqual([
114
+ Permission.ProjectOwner,
115
+ Permission.ProjectAdmin,
116
+ Permission.ProjectMember,
117
+ Permission.OnCallAdmin,
118
+ Permission.OnCallMember,
119
+ Permission.EditProjectOnCallDutyPolicySchedule,
120
+ ]);
121
+ });
122
+
123
+ test("no feed list names a permission the schedule does not (no new Permission members)", () => {
124
+ const schedulePermissions: Set<Permission> = new Set<Permission>([
125
+ ...schedule.createRecordPermissions,
126
+ ...schedule.readRecordPermissions,
127
+ ...schedule.updateRecordPermissions,
128
+ ...schedule.deleteRecordPermissions,
129
+ ]);
130
+
131
+ for (const [, model] of SHARED_FEEDS) {
132
+ for (const column of model.getTableColumns().columns) {
133
+ const accessControl: ColumnAccessControl | null =
134
+ model.getColumnAccessControlFor(column);
135
+
136
+ for (const permission of [
137
+ ...(accessControl?.create || []),
138
+ ...(accessControl?.read || []),
139
+ ...(accessControl?.update || []),
140
+ ]) {
141
+ expect(schedulePermissions.has(permission)).toBe(true);
142
+ }
143
+ }
144
+ }
145
+ });
146
+
147
+ test("every readable column is readable by the whole schedule read list", () => {
148
+ /*
149
+ * A column readable by ProjectMember but not by OnCallViewer would turn a
150
+ * viewer's list request into a permission error on that field - the
151
+ * failure mode DomainRoleTierCoverage.test.ts exists for. Here it is
152
+ * pinned against the schedule's exact list, not just the tier trio.
153
+ */
154
+ for (const [name, model] of SHARED_FEEDS) {
155
+ for (const column of model.getTableColumns().columns) {
156
+ const accessControl: ColumnAccessControl | null =
157
+ model.getColumnAccessControlFor(column);
158
+
159
+ if (!accessControl || accessControl.read.length === 0) {
160
+ continue;
161
+ }
162
+
163
+ expect({ model: name, column, read: accessControl.read }).toEqual({
164
+ model: name,
165
+ column,
166
+ read: schedule.readRecordPermissions,
167
+ });
168
+ }
169
+ }
170
+ });
171
+
172
+ test("every creatable or updatable column uses the schedule's create/update lists", () => {
173
+ for (const [name, model] of SHARED_FEEDS) {
174
+ for (const column of model.getTableColumns().columns) {
175
+ const accessControl: ColumnAccessControl | null =
176
+ model.getColumnAccessControlFor(column);
177
+
178
+ if (!accessControl) {
179
+ continue;
180
+ }
181
+
182
+ if (accessControl.create.length > 0) {
183
+ expect({ model: name, column, create: accessControl.create }).toEqual(
184
+ {
185
+ model: name,
186
+ column,
187
+ create: schedule.createRecordPermissions,
188
+ },
189
+ );
190
+ }
191
+
192
+ if (accessControl.update.length > 0) {
193
+ expect({ model: name, column, update: accessControl.update }).toEqual(
194
+ {
195
+ model: name,
196
+ column,
197
+ update: schedule.updateRecordPermissions,
198
+ },
199
+ );
200
+ }
201
+ }
202
+ }
203
+ });
204
+ });
205
+
206
+ describe("OnCallDutyPolicyScheduleCalendarFeed shape", () => {
207
+ test("is registered in the model index", () => {
208
+ expect(
209
+ (AllModelTypes as Array<ModelType>).includes(
210
+ OnCallDutyPolicyScheduleCalendarFeed,
211
+ ),
212
+ ).toBe(true);
213
+ });
214
+
215
+ test("is tenant scoped by projectId", () => {
216
+ expect(scheduleFeed.getTenantColumn()).toBe("projectId");
217
+ });
218
+
219
+ test("follows the schedule's label scoping through the schedule relation", () => {
220
+ expect(scheduleFeed.canAccessIfCanReadOn).toBe("onCallDutyPolicySchedule");
221
+
222
+ // The relation the scoping follows must exist and point at the schedule.
223
+ expect(
224
+ scheduleFeed.getTableColumnMetadata("onCallDutyPolicySchedule").type,
225
+ ).toBe(TableColumnType.Entity);
226
+ expect(
227
+ scheduleFeed.getTableColumnMetadata("onCallDutyPolicySchedule").modelType,
228
+ ).toBe(OnCallDutyPolicySchedule);
229
+ expect(
230
+ scheduleFeed.getTableColumnMetadata("onCallDutyPolicySchedule")
231
+ .manyToOneRelationColumn,
232
+ ).toBe("onCallDutyPolicyScheduleId");
233
+ });
234
+
235
+ test("the project feed has no single parent to scope by", () => {
236
+ expect(projectFeed.canAccessIfCanReadOn).toBeFalsy();
237
+ expect(projectFeed.getTableColumns().columns).not.toContain(
238
+ "onCallDutyPolicySchedule",
239
+ );
240
+ expect(projectFeed.getTableColumns().columns).not.toContain(
241
+ "onCallDutyPolicyScheduleId",
242
+ );
243
+ });
244
+
245
+ test("is served from its own CRUD route", () => {
246
+ expect(scheduleFeed.getCrudApiPath()?.toString()).toBe(
247
+ "/on-call-duty-policy-schedule-calendar-feed",
248
+ );
249
+ expect(projectFeed.getCrudApiPath()?.toString()).toBe(
250
+ "/project-on-call-calendar-feed",
251
+ );
252
+ });
253
+
254
+ test.each(SHARED_FEEDS)(
255
+ "%s is plan gated like the schedule (Growth for every operation)",
256
+ (_name: string, model: BaseModel) => {
257
+ expect(model.getCreateBillingPlan()).toBe(PlanType.Growth);
258
+ expect(model.getReadBillingPlan()).toBe(PlanType.Growth);
259
+ expect(model.getUpdateBillingPlan()).toBe(PlanType.Growth);
260
+ expect(model.getDeleteBillingPlan()).toBe(PlanType.Growth);
261
+
262
+ expect(model.getCreateBillingPlan()).toBe(
263
+ schedule.getCreateBillingPlan(),
264
+ );
265
+ expect(model.getReadBillingPlan()).toBe(schedule.getReadBillingPlan());
266
+ expect(model.getUpdateBillingPlan()).toBe(
267
+ schedule.getUpdateBillingPlan(),
268
+ );
269
+ expect(model.getDeleteBillingPlan()).toBe(
270
+ schedule.getDeleteBillingPlan(),
271
+ );
272
+ },
273
+ );
274
+
275
+ test.each(SHARED_FEEDS)(
276
+ "%s stays reachable while a subscription is unpaid (links must not go dark)",
277
+ (_name: string, model: BaseModel) => {
278
+ expect(model.allowAccessIfSubscriptionIsUnpaid).toBe(true);
279
+ },
280
+ );
281
+
282
+ test.each(SHARED_FEEDS)(
283
+ "%s is not documented (no OpenAPI / Terraform surface for a secret-bearing table)",
284
+ (_name: string, model: BaseModel) => {
285
+ expect(model.enableDocumentation).toBeFalsy();
286
+ },
287
+ );
288
+ });
@@ -0,0 +1,88 @@
1
+ import OnCallDutyPolicySchedule from "../../Models/DatabaseModels/OnCallDutyPolicySchedule";
2
+ import { ColumnAccessControl } from "../../Types/BaseDatabase/AccessControl";
3
+ import { TableColumnMetadata } from "../../Types/Database/TableColumn";
4
+ import TableColumnType from "../../Types/Database/TableColumnType";
5
+ import { describe, expect, test } from "@jest/globals";
6
+ import { getMetadataArgsStorage } from "typeorm";
7
+ import type { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
8
+
9
+ /*
10
+ * shiftConfigVersion is the SEQUENCE of every calendar VEVENT and part of
11
+ * every feed cache key. Its contract is small and load-bearing: an integer
12
+ * that starts at 0, that readers of the schedule can see (the feed renderer
13
+ * reads it through the schedule relation), and that nobody can set through
14
+ * the API - it moves only by the service's atomic bump.
15
+ */
16
+
17
+ const schedule: OnCallDutyPolicySchedule = new OnCallDutyPolicySchedule();
18
+
19
+ function dbColumn(): ColumnMetadataArgs {
20
+ const column: ColumnMetadataArgs | undefined =
21
+ getMetadataArgsStorage().columns.find((entry: ColumnMetadataArgs) => {
22
+ return (
23
+ entry.target === OnCallDutyPolicySchedule &&
24
+ entry.propertyName === "shiftConfigVersion"
25
+ );
26
+ });
27
+
28
+ if (!column) {
29
+ throw new Error("OnCallDutyPolicySchedule.shiftConfigVersion is missing");
30
+ }
31
+
32
+ return column;
33
+ }
34
+
35
+ describe("OnCallDutyPolicySchedule.shiftConfigVersion", () => {
36
+ test("exists as a Number column", () => {
37
+ expect(schedule.getTableColumns().columns).toContain("shiftConfigVersion");
38
+
39
+ const metadata: TableColumnMetadata =
40
+ schedule.getTableColumnMetadata("shiftConfigVersion");
41
+
42
+ expect(metadata.type).toBe(TableColumnType.Number);
43
+ });
44
+
45
+ test("starts at 0 - in the model metadata and at the database", () => {
46
+ const metadata: TableColumnMetadata =
47
+ schedule.getTableColumnMetadata("shiftConfigVersion");
48
+
49
+ expect(metadata.isDefaultValueColumn).toBe(true);
50
+ expect(metadata.defaultValue).toBe(0);
51
+
52
+ expect(dbColumn().options.default).toBe(0);
53
+ expect(dbColumn().options.nullable).toBe(false);
54
+ });
55
+
56
+ test("is readable by everyone who may read the schedule, incl. through a relation", () => {
57
+ const accessControl: ColumnAccessControl | null =
58
+ schedule.getColumnAccessControlFor("shiftConfigVersion");
59
+
60
+ expect(accessControl?.read).toEqual(schedule.readRecordPermissions);
61
+ expect(accessControl?.read.length).toBeGreaterThan(0);
62
+
63
+ expect(
64
+ schedule.getTableColumnMetadata("shiftConfigVersion")
65
+ .canReadOnRelationQuery,
66
+ ).toBe(true);
67
+ });
68
+
69
+ test("cannot be set through the API on create or update", () => {
70
+ const accessControl: ColumnAccessControl | null =
71
+ schedule.getColumnAccessControlFor("shiftConfigVersion");
72
+
73
+ expect(accessControl?.create).toEqual([]);
74
+ expect(accessControl?.update).toEqual([]);
75
+ });
76
+
77
+ test("does not block a schedule create that omits it (required + default)", () => {
78
+ /*
79
+ * DatabaseService.checkRequiredFields skips a required column that is a
80
+ * default-value column, so `required: true` here documents intent
81
+ * without forcing every creator to send a 0.
82
+ */
83
+ expect(schedule.getTableColumnMetadata("shiftConfigVersion").required).toBe(
84
+ true,
85
+ );
86
+ expect(schedule.isDefaultValueColumn("shiftConfigVersion")).toBe(true);
87
+ });
88
+ });