@@ -142,5 +142,201 @@ void main() {
142142
143143 expect (results2[0 ]['detail' ], contains ('SCAN' ));
144144 });
145+
146+ test ('Validation runs on setup' , () async {
147+ final schema = Schema ([
148+ Table ('#assets' , [
149+ Column .text ('name' ),
150+ ]),
151+ ]);
152+
153+ try {
154+ await testUtils.setupPowerSync (path: path, schema: schema);
155+ } catch (e) {
156+ expect (
157+ e,
158+ isA <AssertionError >().having ((e) => e.message, 'message' ,
159+ 'Invalid characters in table name: #assets' ));
160+ }
161+ });
162+
163+ test ('Validation runs on update' , () async {
164+ final schema = Schema ([
165+ Table ('works' , [
166+ Column .text ('name' ),
167+ ]),
168+ ]);
169+
170+ final powersync =
171+ await testUtils.setupPowerSync (path: path, schema: schema);
172+
173+ final schema2 = Schema ([
174+ Table ('#notworking' , [
175+ Column .text ('created_at' ),
176+ ]),
177+ ]);
178+
179+ try {
180+ powersync.updateSchema (schema2);
181+ } catch (e) {
182+ expect (
183+ e,
184+ isA <AssertionError >().having ((e) => e.message, 'message' ,
185+ 'Invalid characters in table name: #notworking' ));
186+ }
187+ });
188+ });
189+
190+ group ('Table' , () {
191+ test ('Create a synced table' , () {
192+ final table = Table ('users' , [
193+ Column ('name' , ColumnType .text),
194+ Column ('age' , ColumnType .integer),
195+ ]);
196+
197+ expect (table.name, equals ('users' ));
198+ expect (table.columns.length, equals (2 ));
199+ expect (table.localOnly, isFalse);
200+ expect (table.insertOnly, isFalse);
201+ expect (table.internalName, equals ('ps_data__users' ));
202+ expect (table.viewName, equals ('users' ));
203+ });
204+
205+ test ('Create a local-only table' , () {
206+ final table = Table .localOnly (
207+ 'local_users' ,
208+ [
209+ Column ('name' , ColumnType .text),
210+ ],
211+ viewName: 'local_user_view' );
212+
213+ expect (table.name, equals ('local_users' ));
214+ expect (table.localOnly, isTrue);
215+ expect (table.insertOnly, isFalse);
216+ expect (table.internalName, equals ('ps_data_local__local_users' ));
217+ expect (table.viewName, equals ('local_user_view' ));
218+ });
219+
220+ test ('Create an insert-only table' , () {
221+ final table = Table .insertOnly ('logs' , [
222+ Column ('message' , ColumnType .text),
223+ Column ('timestamp' , ColumnType .integer),
224+ ]);
225+
226+ expect (table.name, equals ('logs' ));
227+ expect (table.localOnly, isFalse);
228+ expect (table.insertOnly, isTrue);
229+ expect (table.internalName, equals ('ps_data__logs' ));
230+ expect (table.indexes, isEmpty);
231+ });
232+
233+ test ('Access column by name' , () {
234+ final table = Table ('products' , [
235+ Column ('name' , ColumnType .text),
236+ Column ('price' , ColumnType .real),
237+ ]);
238+
239+ expect (table['name' ].type, equals (ColumnType .text));
240+ expect (table['price' ].type, equals (ColumnType .real));
241+ expect (() => table['nonexistent' ], throwsStateError);
242+ });
243+
244+ test ('Validate table name' , () {
245+ final invalidTableName =
246+ Table ('#invalid_table_name' , [Column ('name' , ColumnType .text)]);
247+
248+ expect (
249+ () => invalidTableName.validate (),
250+ throwsA (
251+ isA <AssertionError >().having (
252+ (e) => e.message,
253+ 'message' ,
254+ 'Invalid characters in table name: #invalid_table_name' ,
255+ ),
256+ ),
257+ );
258+ });
259+
260+ test ('Validate view name' , () {
261+ final invalidTableName = Table (
262+ 'valid_table_name' , [Column ('name' , ColumnType .text)],
263+ viewName: '#invalid_view_name' );
264+
265+ expect (
266+ () => invalidTableName.validate (),
267+ throwsA (
268+ isA <AssertionError >().having (
269+ (e) => e.message,
270+ 'message' ,
271+ 'Invalid characters in view name: #invalid_view_name' ,
272+ ),
273+ ),
274+ );
275+ });
276+
277+ test ('Validate table definition' , () {
278+ final validTable = Table ('valid_table' , [
279+ Column ('name' , ColumnType .text),
280+ Column ('age' , ColumnType .integer),
281+ ]);
282+
283+ expect (() => validTable.validate (), returnsNormally);
284+ });
285+
286+ test ('Table with id column' , () {
287+ final invalidTable = Table ('invalid_table' , [
288+ Column ('id' , ColumnType .integer), // Duplicate 'id' column
289+ Column ('name' , ColumnType .text),
290+ ]);
291+
292+ expect (
293+ () => invalidTable.validate (),
294+ throwsA (
295+ isA <AssertionError >().having (
296+ (e) => e.message,
297+ 'message' ,
298+ 'invalid_table: id column is automatically added, custom id columns are not supported' ,
299+ ),
300+ ),
301+ );
302+ });
303+
304+ test ('Table with too many columns' , () {
305+ final List <Column > manyColumns = List .generate (
306+ 64 , // Exceeds MAX_NUMBER_OF_COLUMNS
307+ (index) => Column ('col$index ' , ColumnType .text),
308+ );
309+
310+ final tableTooManyColumns = Table ('too_many_columns' , manyColumns);
311+
312+ expect (
313+ () => tableTooManyColumns.validate (),
314+ throwsA (
315+ isA <AssertionError >().having (
316+ (e) => e.message,
317+ 'message' ,
318+ 'Table too_many_columns has more than 63 columns, which is not supported' ,
319+ ),
320+ ),
321+ );
322+ });
323+
324+ test ('toJson method' , () {
325+ final table = Table ('users' , [
326+ Column ('name' , ColumnType .text),
327+ Column ('age' , ColumnType .integer),
328+ ], indexes: [
329+ Index ('name_index' , [IndexedColumn ('name' )])
330+ ]);
331+
332+ final json = table.toJson ();
333+
334+ expect (json['name' ], equals ('users' ));
335+ expect (json['view_name' ], isNull);
336+ expect (json['local_only' ], isFalse);
337+ expect (json['insert_only' ], isFalse);
338+ expect (json['columns' ].length, equals (2 ));
339+ expect (json['indexes' ].length, equals (1 ));
340+ });
145341 });
146342}
0 commit comments