This commit is contained in:
2026-10-16 01:25:50 +05:30
commit c60ea2d370
89 changed files with 4661 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:kickkings_support/models/account.dart';
void main() {
test('admins can read and write support', () {
const account = Account(
id: '1',
username: 'ada',
isAdmin: true,
permissions: {},
);
expect(account.canReadSupport, isTrue);
expect(account.canWriteSupport, isTrue);
});
test('support read does not grant write', () {
final account = Account.fromJson({
'id': '1',
'username': 'ada',
'isAdmin': false,
'permissions': {
'support': {'read': true, 'write': false},
},
});
expect(account.canReadSupport, isTrue);
expect(account.canWriteSupport, isFalse);
});
test('missing support permission cannot enter the inbox', () {
final account = Account.fromJson({
'id': '1',
'username': 'ada',
'isAdmin': false,
'permissions': {
'players': {'read': true},
},
});
expect(account.canReadSupport, isFalse);
expect(account.canWriteSupport, isFalse);
});
test('account json round trip keeps support flags', () {
final account = Account.fromJson({
'id': 'acct',
'username': 'ada',
'isAdmin': false,
'permissions': {
'support': {'read': true, 'write': true},
},
});
final restored = Account.fromJson(account.toJson());
expect(restored.canReadSupport, isTrue);
expect(restored.canWriteSupport, isTrue);
expect(restored.username, 'ada');
});
}
+42
View File
@@ -0,0 +1,42 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:kickkings_support/models/api_exception.dart';
void main() {
test('reads the error string', () {
final error = ApiException.fromBody(
statusCode: 401,
body: {'error': 'Invalid credentials'},
);
expect(error.statusCode, 401);
expect(error.message, 'Invalid credentials');
expect(error.isUnauthorized, isTrue);
});
test('appends retryAfterSeconds', () {
final error = ApiException.fromBody(
statusCode: 429,
body: {
'error': 'Too many failed login attempts. Try again later.',
'retryAfterSeconds': 900,
},
);
expect(error.retryAfterSeconds, 900);
expect(
error.message,
'Too many failed login attempts. Try again later. Retry after 900 seconds.',
);
});
test('uses the fallback when the body has no error', () {
final error = ApiException.fromBody(
statusCode: 503,
body: 'unavailable',
fallback: 'Could not reach the server.',
);
expect(error.message, 'Could not reach the server.');
expect(error.retryAfterSeconds, isNull);
});
}
+103
View File
@@ -0,0 +1,103 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:kickkings_support/config.dart';
import 'package:kickkings_support/models/login_result.dart';
import 'package:kickkings_support/models/support_message.dart';
import 'package:kickkings_support/models/support_ticket.dart';
void main() {
test('normalizes a base url', () {
expect(
normalizeBaseUrl('https://kickkings.playpoolstudios.com/'),
'https://kickkings.playpoolstudios.com',
);
expect(normalizeBaseUrl(' https://example.com/// '), 'https://example.com');
});
test('ticket query omits empty filters', () {
expect(ticketListQuery(status: 'open'), {'status': 'open'});
expect(ticketListQuery(status: 'closed', category: 'bug'), {
'status': 'closed',
'category': 'bug',
});
expect(ticketListQuery(), isEmpty);
});
test('parses a ticket and a thread', () {
final ticket = SupportTicket.fromJson({
'id': 'ticket-1',
'userId': 42,
'username': 'playername',
'category': 'bug',
'categoryLabel': 'Report Bug',
'status': 'open',
'createdAt': '2026-10-15T12:00:00.000Z',
'updatedAt': '2026-10-15T12:00:00.000Z',
'lastMessageAt': '2026-10-15T12:05:00.000Z',
'lastMessagePreview': 'The match froze',
});
expect(ticket.userId, 42);
expect(ticket.displayCategory, 'Report Bug');
expect(ticket.isClosed, isFalse);
expect(
ticket.lastMessageAt.toUtc().toIso8601String(),
'2026-10-15T12:05:00.000Z',
);
final thread = SupportThread.fromJson({
'ticket': {
'id': 'ticket-1',
'userId': 42,
'username': 'playername',
'category': 'bug',
'categoryLabel': 'Report Bug',
'status': 'closed',
'createdAt': '2026-10-15T12:00:00.000Z',
'updatedAt': '2026-10-15T12:10:00.000Z',
'lastMessageAt': '2026-10-15T12:10:00.000Z',
'lastMessagePreview': 'We are looking into this.',
},
'messages': [
{
'id': 'message-1',
'ticketId': 'ticket-1',
'authorKind': 'player',
'authorName': 'playername',
'body': 'The match froze',
'createdAt': '2026-10-15T12:00:00.000Z',
},
{
'id': 'message-2',
'ticketId': 'ticket-1',
'authorKind': 'staff',
'authorName': 'ada',
'body': 'We are looking into this.',
'createdAt': '2026-10-15T12:10:00.000Z',
},
],
});
expect(thread.ticket.isClosed, isTrue);
expect(thread.messages, hasLength(2));
expect(thread.messages.first.isStaff, isFalse);
expect(thread.messages.last.isStaff, isTrue);
});
test('parses a login result', () {
final result = LoginResult.fromJson({
'ok': true,
'token': 'session-token',
'account': {
'id': 'acct',
'username': 'ada',
'isAdmin': false,
'permissions': {
'support': {'read': true, 'write': true},
},
},
});
expect(result.token, 'session-token');
expect(result.account.canWriteSupport, isTrue);
});
}
+20
View File
@@ -0,0 +1,20 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:kickkings_support/models/support_message.dart';
void main() {
test('rejects an empty reply', () {
expect(validateReplyBody(' '), 'Enter a message.');
});
test('accepts a trimmed reply up to 4000 characters', () {
expect(validateReplyBody(' hello '), isNull);
expect(validateReplyBody('a' * maxReplyLength), isNull);
});
test('rejects a reply over 4000 characters', () {
expect(
validateReplyBody('a' * (maxReplyLength + 1)),
'Message must be 4000 characters or fewer.',
);
});
}