Skip to content

Commit a344728

Browse files
committed
Add plugin test, prevent plugins from editing plugins #73
1 parent a338ff6 commit a344728

File tree

18 files changed

+402
-124
lines changed

18 files changed

+402
-124
lines changed

atomic-plugin/src/bindings.rs

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ pub unsafe fn _export_before_commit_cabi<T: Guest>(
289289
arg5: usize,
290290
arg6: *mut u8,
291291
arg7: usize,
292+
arg8: i32,
292293
) -> *mut u8 {
293294
#[cfg(target_arch = "wasm32")]
294295
_rt::run_ctors_once();
@@ -307,6 +308,7 @@ pub unsafe fn _export_before_commit_cabi<T: Guest>(
307308
subject: _rt::string_lift(bytes2),
308309
json_ad: _rt::string_lift(bytes3),
309310
},
311+
is_new: _rt::bool_lift(arg8 as u8),
310312
});
311313
let ptr5 = (&raw mut _RET_AREA.0).cast::<u8>();
312314
match result4 {
@@ -357,6 +359,7 @@ pub unsafe fn _export_after_commit_cabi<T: Guest>(
357359
arg5: usize,
358360
arg6: *mut u8,
359361
arg7: usize,
362+
arg8: i32,
360363
) -> *mut u8 {
361364
#[cfg(target_arch = "wasm32")]
362365
_rt::run_ctors_once();
@@ -375,6 +378,7 @@ pub unsafe fn _export_after_commit_cabi<T: Guest>(
375378
subject: _rt::string_lift(bytes2),
376379
json_ad: _rt::string_lift(bytes3),
377380
},
381+
is_new: _rt::bool_lift(arg8 as u8),
378382
});
379383
let ptr5 = (&raw mut _RET_AREA.0).cast::<u8>();
380384
match result4 {
@@ -443,18 +447,19 @@ macro_rules! __export_world_class_extender_cabi {
443447
$($path_to_types)*:: __post_return_on_resource_get::<$ty > (arg0) } } #[unsafe
444448
(export_name = "before-commit")] unsafe extern "C" fn export_before_commit(arg0 :
445449
* mut u8, arg1 : usize, arg2 : * mut u8, arg3 : usize, arg4 : * mut u8, arg5 :
446-
usize, arg6 : * mut u8, arg7 : usize,) -> * mut u8 { unsafe {
450+
usize, arg6 : * mut u8, arg7 : usize, arg8 : i32,) -> * mut u8 { unsafe {
447451
$($path_to_types)*:: _export_before_commit_cabi::<$ty > (arg0, arg1, arg2, arg3,
448-
arg4, arg5, arg6, arg7) } } #[unsafe (export_name = "cabi_post_before-commit")]
449-
unsafe extern "C" fn _post_return_before_commit(arg0 : * mut u8,) { unsafe {
450-
$($path_to_types)*:: __post_return_before_commit::<$ty > (arg0) } } #[unsafe
451-
(export_name = "after-commit")] unsafe extern "C" fn export_after_commit(arg0 : *
452-
mut u8, arg1 : usize, arg2 : * mut u8, arg3 : usize, arg4 : * mut u8, arg5 :
453-
usize, arg6 : * mut u8, arg7 : usize,) -> * mut u8 { unsafe {
454-
$($path_to_types)*:: _export_after_commit_cabi::<$ty > (arg0, arg1, arg2, arg3,
455-
arg4, arg5, arg6, arg7) } } #[unsafe (export_name = "cabi_post_after-commit")]
456-
unsafe extern "C" fn _post_return_after_commit(arg0 : * mut u8,) { unsafe {
457-
$($path_to_types)*:: __post_return_after_commit::<$ty > (arg0) } } };
452+
arg4, arg5, arg6, arg7, arg8) } } #[unsafe (export_name =
453+
"cabi_post_before-commit")] unsafe extern "C" fn _post_return_before_commit(arg0
454+
: * mut u8,) { unsafe { $($path_to_types)*:: __post_return_before_commit::<$ty >
455+
(arg0) } } #[unsafe (export_name = "after-commit")] unsafe extern "C" fn
456+
export_after_commit(arg0 : * mut u8, arg1 : usize, arg2 : * mut u8, arg3 : usize,
457+
arg4 : * mut u8, arg5 : usize, arg6 : * mut u8, arg7 : usize, arg8 : i32,) -> *
458+
mut u8 { unsafe { $($path_to_types)*:: _export_after_commit_cabi::<$ty > (arg0,
459+
arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) } } #[unsafe (export_name =
460+
"cabi_post_after-commit")] unsafe extern "C" fn _post_return_after_commit(arg0 :
461+
* mut u8,) { unsafe { $($path_to_types)*:: __post_return_after_commit::<$ty >
462+
(arg0) } } };
458463
};
459464
}
460465
#[doc(hidden)]
@@ -535,6 +540,8 @@ pub mod atomic {
535540
pub subject: _rt::String,
536541
pub commit_json: _rt::String,
537542
pub snapshot: ResourceJson,
543+
/// True if this is the first commit for the resource.
544+
pub is_new: bool,
538545
}
539546
impl ::core::fmt::Debug for CommitContext {
540547
fn fmt(
@@ -545,6 +552,7 @@ pub mod atomic {
545552
.field("subject", &self.subject)
546553
.field("commit-json", &self.commit_json)
547554
.field("snapshot", &self.snapshot)
555+
.field("is-new", &self.is_new)
548556
.finish()
549557
}
550558
}
@@ -999,6 +1007,17 @@ mod _rt {
9991007
wit_bindgen_rt::run_ctors_once();
10001008
}
10011009
pub use alloc_crate::alloc;
1010+
pub unsafe fn bool_lift(val: u8) -> bool {
1011+
if cfg!(debug_assertions) {
1012+
match val {
1013+
0 => false,
1014+
1 => true,
1015+
_ => panic!("invalid bool discriminant"),
1016+
}
1017+
} else {
1018+
val != 0
1019+
}
1020+
}
10021021
extern crate alloc as alloc_crate;
10031022
}
10041023
/// Generates `#[unsafe(no_mangle)]` functions to export the specified type as
@@ -1034,29 +1053,29 @@ pub(crate) use __export_class_extender_impl as export;
10341053
#[unsafe(link_section = "component-type:wit-bindgen:0.41.0:atomic:class-extender@0.1.0:class-extender:encoded world")]
10351054
#[doc(hidden)]
10361055
#[allow(clippy::octal_escapes)]
1037-
pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 994] = *b"\
1038-
\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xdd\x06\x01A\x02\x01\
1056+
pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 1002] = *b"\
1057+
\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xe5\x06\x01A\x02\x01\
10391058
A\x17\x01B\x0b\x01r\x01\x07subjects\x04\0\x0catomic-agent\x03\0\0\x01r\x02\x07su\
10401059
bjects\x07json-ads\x04\0\x0dresource-json\x03\0\x02\x01p\x03\x01r\x02\x07primary\
10411060
\x03\x0areferenced\x04\x04\0\x11resource-response\x03\0\x05\x01r\x04\x0brequest-\
10421061
urls\x11requested-subjects\x0dagent-subjects\x08snapshot\x03\x04\0\x0bget-contex\
1043-
t\x03\0\x07\x01r\x03\x07subjects\x0bcommit-jsons\x08snapshot\x03\x04\0\x0ecommit\
1044-
-context\x03\0\x09\x03\0!atomic:class-extender/types@0.1.0\x05\0\x02\x03\0\0\x11\
1045-
resource-response\x03\0\x11resource-response\x03\0\x01\x02\x03\0\0\x0bget-contex\
1046-
t\x03\0\x0bget-context\x03\0\x03\x02\x03\0\0\x0ecommit-context\x03\0\x0ecommit-c\
1047-
ontext\x03\0\x05\x02\x03\0\0\x0dresource-json\x02\x03\0\0\x0catomic-agent\x01B\x12\
1048-
\x02\x03\x02\x01\x07\x04\0\x0dresource-json\x03\0\0\x02\x03\x02\x01\x08\x04\0\x0c\
1049-
atomic-agent\x03\0\x02\x01ks\x01j\x01\x01\x01s\x01@\x02\x07subjects\x05agent\x04\
1050-
\0\x05\x04\0\x0cget-resource\x01\x06\x01p\x01\x01j\x01\x07\x01s\x01@\x03\x08prop\
1051-
ertys\x05values\x05agent\x04\0\x08\x04\0\x05query\x01\x09\x01@\0\0s\x04\0\x10get\
1052-
-plugin-agent\x01\x0a\x04\0\x0aget-config\x01\x0a\x01j\0\x01s\x01@\x01\x06commit\
1053-
s\0\x0b\x04\0\x06commit\x01\x0c\x03\0\x20atomic:class-extender/host@0.1.0\x05\x09\
1054-
\x01ps\x01@\0\0\x0a\x04\0\x09class-url\x01\x0b\x01k\x02\x01j\x01\x0c\x01s\x01@\x01\
1055-
\x03ctx\x04\0\x0d\x04\0\x0fon-resource-get\x01\x0e\x01j\0\x01s\x01@\x01\x03ctx\x06\
1056-
\0\x0f\x04\0\x0dbefore-commit\x01\x10\x04\0\x0cafter-commit\x01\x10\x04\0*atomic\
1057-
:class-extender/class-extender@0.1.0\x04\0\x0b\x14\x01\0\x0eclass-extender\x03\0\
1058-
\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\x070.227.1\x10wit-bi\
1059-
ndgen-rust\x060.41.0";
1062+
t\x03\0\x07\x01r\x04\x07subjects\x0bcommit-jsons\x08snapshot\x03\x06is-new\x7f\x04\
1063+
\0\x0ecommit-context\x03\0\x09\x03\0!atomic:class-extender/types@0.1.0\x05\0\x02\
1064+
\x03\0\0\x11resource-response\x03\0\x11resource-response\x03\0\x01\x02\x03\0\0\x0b\
1065+
get-context\x03\0\x0bget-context\x03\0\x03\x02\x03\0\0\x0ecommit-context\x03\0\x0e\
1066+
commit-context\x03\0\x05\x02\x03\0\0\x0dresource-json\x02\x03\0\0\x0catomic-agen\
1067+
t\x01B\x12\x02\x03\x02\x01\x07\x04\0\x0dresource-json\x03\0\0\x02\x03\x02\x01\x08\
1068+
\x04\0\x0catomic-agent\x03\0\x02\x01ks\x01j\x01\x01\x01s\x01@\x02\x07subjects\x05\
1069+
agent\x04\0\x05\x04\0\x0cget-resource\x01\x06\x01p\x01\x01j\x01\x07\x01s\x01@\x03\
1070+
\x08propertys\x05values\x05agent\x04\0\x08\x04\0\x05query\x01\x09\x01@\0\0s\x04\0\
1071+
\x10get-plugin-agent\x01\x0a\x04\0\x0aget-config\x01\x0a\x01j\0\x01s\x01@\x01\x06\
1072+
commits\0\x0b\x04\0\x06commit\x01\x0c\x03\0\x20atomic:class-extender/host@0.1.0\x05\
1073+
\x09\x01ps\x01@\0\0\x0a\x04\0\x09class-url\x01\x0b\x01k\x02\x01j\x01\x0c\x01s\x01\
1074+
@\x01\x03ctx\x04\0\x0d\x04\0\x0fon-resource-get\x01\x0e\x01j\0\x01s\x01@\x01\x03\
1075+
ctx\x06\0\x0f\x04\0\x0dbefore-commit\x01\x10\x04\0\x0cafter-commit\x01\x10\x04\0\
1076+
*atomic:class-extender/class-extender@0.1.0\x04\0\x0b\x14\x01\0\x0eclass-extende\
1077+
r\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\x070.227.1\x10\
1078+
wit-bindgen-rust\x060.41.0";
10601079
#[inline(never)]
10611080
#[doc(hidden)]
10621081
pub fn __link_custom_section_describing_imports() {

atomic-plugin/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,12 @@ pub trait ClassExtender {
145145
}
146146

147147
/// Called before a Commit that targets the class is persisted. If you return an error, the commit will be rejected.
148-
fn before_commit(_commit: &Commit, _snapshot: &Resource) -> Result<(), String> {
148+
fn before_commit(_commit: &Commit, _snapshot: &Resource, _is_new: bool) -> Result<(), String> {
149149
Ok(())
150150
}
151151

152152
/// Called after a Commit that targets the class has been applied. Returning an error will not cancel the commit.
153-
fn after_commit(_commit: &Commit, _resource: &Resource) -> Result<(), String> {
153+
fn after_commit(_commit: &Commit, _resource: &Resource, _is_new: bool) -> Result<(), String> {
154154
Ok(())
155155
}
156156
}
@@ -185,14 +185,14 @@ impl<T: ClassExtender> Guest for PluginWrapper<T> {
185185
let commit: Commit = serde_json::from_str(&ctx.commit_json).map_err(|e| e.to_string())?;
186186
let snapshot: Resource = Resource::try_from(ctx.snapshot)?;
187187

188-
T::before_commit(&commit, &snapshot)
188+
T::before_commit(&commit, &snapshot, ctx.is_new)
189189
}
190190

191191
fn after_commit(ctx: CommitContext) -> Result<(), String> {
192192
let commit: Commit = serde_json::from_str(&ctx.commit_json).map_err(|e| e.to_string())?;
193193
let snapshot: Resource = Resource::try_from(ctx.snapshot)?;
194194

195-
T::after_commit(&commit, &snapshot)
195+
T::after_commit(&commit, &snapshot, ctx.is_new)
196196
}
197197
}
198198

atomic-plugin/wit/class-extender.wit

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ interface types {
4747
subject: string,
4848
commit-json: string,
4949
snapshot: resource-json,
50+
/// True if this is the first commit for the resource.
51+
is-new: bool,
5052
}
5153
}
5254

browser/data-browser/src/components/forms/InputStyles.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const InputWrapper = styled.div<InputWrapperProps>`
4040
--border-color: ${({ $invalid, theme }) =>
4141
$invalid ? theme.colors.alert : theme.colors.bg2};
4242
border: solid 1px var(--border-color);
43+
background-color: ${props => props.theme.colors.bg};
4344
border-radius: ${props => props.theme.radius};
4445
overflow: hidden;
4546
align-items: center;

browser/data-browser/src/views/FolderPage/ListView.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ const StyledTable = styled.table`
128128
th:last-child {
129129
padding-right: 2rem;
130130
}
131+
132+
td,
133+
th {
134+
border: none !important;
135+
}
131136
`;
132137

133138
const IconWrapper = styled.span`
100 KB
Binary file not shown.

browser/e2e/tests/plugin.spec.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import test, { expect } from '@playwright/test';
2+
import {
3+
before,
4+
inDialog,
5+
newDrive,
6+
newResource,
7+
setTitle,
8+
sidebarDriveButtonId,
9+
signIn,
10+
testFilePath,
11+
} from './test-utils';
12+
13+
test.describe('Plugins', () => {
14+
test.beforeEach(before);
15+
16+
test('install a plugin', async ({ page }) => {
17+
await signIn(page);
18+
await newDrive(page);
19+
20+
// Create a folder called 'Problem
21+
await newResource('folder', page);
22+
await setTitle(page, 'Problem');
23+
24+
await page.getByTestId(sidebarDriveButtonId).click();
25+
26+
// Upload a plugin
27+
const fileChooserPromise = page.waitForEvent('filechooser');
28+
await page.getByRole('main').getByText('Upload Plugin').click();
29+
const fileChooser = await fileChooserPromise;
30+
31+
await fileChooser.setFiles(testFilePath('test-plugin.zip'));
32+
33+
await inDialog(page, async (dialog, closeWith) => {
34+
await expect(
35+
dialog.getByRole('heading', { name: 'Add Plugin' }),
36+
).toBeVisible();
37+
await expect(
38+
dialog.getByText('ontola/test-plugin', { exact: true }),
39+
).toBeVisible();
40+
await expect(dialog.getByText('v1.0.0', { exact: true })).toBeVisible();
41+
await expect(dialog.getByText('Storage', { exact: true })).toBeVisible();
42+
43+
// Check if the install button correctly disables when the config is invalid
44+
const installButton = dialog.getByRole('button', { name: 'Install' });
45+
await expect(installButton).toBeEnabled();
46+
47+
await dialog.getByLabel('Config').fill('{"folderPrefix": 5}');
48+
49+
await expect(installButton).toBeDisabled();
50+
51+
await dialog.getByLabel('Config').fill('{"folderPrefix": "My"}');
52+
53+
await expect(installButton).toBeEnabled();
54+
55+
// Install the plugin
56+
await closeWith('Install');
57+
});
58+
59+
await expect(
60+
page.getByRole('link', { name: 'ontola/test-plugin' }),
61+
).toBeVisible();
62+
await page.reload();
63+
64+
await expect(page.getByText('My Problem', { exact: true })).toBeVisible();
65+
66+
await page.getByRole('link', { name: 'ontola/test-plugin' }).click();
67+
68+
// Update the config
69+
await page.getByLabel('Config').fill('{"folderPrefix": "Not My"}');
70+
await page.getByRole('button', { name: 'Save' }).click();
71+
await page.reload();
72+
73+
await expect(
74+
page.getByText('Not My Problem', { exact: true }),
75+
'Plugin did not react to config change',
76+
).toBeVisible();
77+
78+
// Uninstall the plugin
79+
await page.getByRole('button', { name: 'Uninstall' }).click();
80+
81+
await inDialog(page, async (dialog, closeWith) => {
82+
await expect(
83+
dialog.getByRole('heading', { name: 'Uninstall Plugin' }),
84+
).toBeVisible();
85+
await closeWith('Uninstall');
86+
});
87+
88+
await expect(page.getByText('Plugin uninstalled')).toBeVisible();
89+
90+
await page.reload();
91+
92+
await expect(page.getByText('Problem', { exact: true })).toBeVisible();
93+
await expect(page.getByText('No plugins installed')).toBeVisible();
94+
});
95+
});

0 commit comments

Comments
 (0)