diff --git a/tests/src/app/modules/core/core.sapient.test.js b/tests/src/app/modules/core/core.sapient.test.js
new file mode 100644
index 0000000..96e0435
--- /dev/null
+++ b/tests/src/app/modules/core/core.sapient.test.js
@@ -0,0 +1,93 @@
+const angular = require('angular');
+require('angular-mocks');
+
+// Mock dependencies
+const mockConfig = { appTitle: 'Test App' };
+const mockShortHistory = { init: jest.fn() };
+const mockAuthorize = { checkAccess: jest.fn() };
+const mockSession = {};
+
+// Mock browser globals
+global.window = {};
+global.document = {
+ querySelector: jest.fn()
+};
+global.$ = jest.fn(() => ({
+ toggleClass: jest.fn()
+}));
+
+// Import the module containing the controller
+require('../../../../../src/app/modules/core/core');
+
+describe('AppController', () => {
+ let $controller, $rootScope, $scope, $state, controller;
+
+ beforeEach(() => {
+ angular.mock.module('app.core');
+
+ angular.mock.module($provide => {
+ $provide.value('config', mockConfig);
+ $provide.value('shortHistory', mockShortHistory);
+ $provide.value('authorize', mockAuthorize);
+ $provide.value('session', mockSession);
+ });
+
+ angular.mock.inject((_$controller_, _$rootScope_, _$state_) => {
+ $controller = _$controller_;
+ $rootScope = _$rootScope_;
+ $state = _$state_;
+ $scope = $rootScope.$new();
+ });
+
+ controller = $controller('App', {
+ $scope: $scope,
+ $state: $state,
+ $rootScope: $rootScope,
+ config: mockConfig,
+ shortHistory: mockShortHistory,
+ authorize: mockAuthorize,
+ session: mockSession
+ });
+ });
+
+ it('should set the title from config', () => {
+ expect(controller.title).toBe(mockConfig.appTitle);
+ });
+
+ it('should set app and $state on $scope', () => {
+ expect($scope.app).toBe(mockConfig);
+ expect($scope.$state).toBe($state);
+ });
+
+ it('should call authorize.checkAccess on $stateChangeStart', () => {
+ const toState = { name: 'testState' };
+ const toParams = { id: 1 };
+ $rootScope.$broadcast('$stateChangeStart', toState, toParams);
+ expect(mockAuthorize.checkAccess).toHaveBeenCalledWith(expect.any(Object), toState, toParams);
+ });
+
+ it('should toggle nav-shown class on $stateChangeSuccess', () => {
+ $scope.$broadcast('$stateChangeSuccess');
+ expect($('body').toggleClass).toHaveBeenCalledWith('nav-shown', false);
+ });
+
+ it('should set currentUser on $userSet event', () => {
+ const user = { name: 'Test User' };
+ $rootScope.$broadcast('$userSet', user);
+ expect(controller.currentUser).toBe(user);
+ });
+
+ it('should initialize shortHistory', () => {
+ expect(mockShortHistory.init).toHaveBeenCalledWith($scope);
+ });
+
+ // Additional test to cover $stateChangeStart with different parameters
+ it('should call authorize.checkAccess with correct parameters on $stateChangeStart', () => {
+ const toState = { name: 'anotherState' };
+ const toParams = { id: 2, extra: 'param' };
+ const fromState = { name: 'previousState' };
+ const fromParams = { id: 1 };
+ $rootScope.$broadcast('$stateChangeStart', toState, toParams, fromState, fromParams);
+ expect(mockAuthorize.checkAccess).toHaveBeenCalledWith(expect.any(Object), toState, toParams);
+ });
+});
\ No newline at end of file
diff --git a/tests/src/app/modules/dashboard/dashboard.sapient.test.js b/tests/src/app/modules/dashboard/dashboard.sapient.test.js
new file mode 100644
index 0000000..b4a0f68
--- /dev/null
+++ b/tests/src/app/modules/dashboard/dashboard.sapient.test.js
@@ -0,0 +1,127 @@
+// Mock browser globals
+global.window = {};
+global.document = {
+ head: {
+ appendChild: jest.fn()
+ }
+};
+
+// Mock Angular and its dependencies
+const mockAngular = {
+ module: jest.fn(() => ({ controller: jest.fn() })),
+ element: jest.fn(() => ({
+ prepend: jest.fn()
+ })),
+ $$csp: jest.fn(() => false)
+};
+
+global.angular = mockAngular;
+
+// Mock $sce service
+const mockSce = {
+ trustAsHtml: jest.fn(html => ({ __html: html })),
+};
+
+// Mock posts and postsUtils
+const mockPosts = [
+ { id: 1, title: 'Post 1', content: 'Content 1', lastEdited: new Date('2023-05-01') },
+ { id: 2, title: 'Post 2', content: 'Content 2', lastEdited: new Date('2023-05-15') },
+];
+
+const mockPostsUtils = {
+ postsDuringInterval: jest.fn(),
+ lastEdited: jest.fn(),
+ recent: jest.fn(),
+};
+
+// Mock the module function
+jest.mock('../../../../../src/app/modules/dashboard/dashboard.js', () => {
+ return {
+ dashboardController: ($scope, $sce, posts, postsUtils) => {
+ $scope.posts = posts;
+ $scope.postsLastMonth = postsUtils.postsDuringInterval(posts, 30);
+ $scope.lastEditedPost = postsUtils.lastEdited(posts);
+ $scope.postsRecently = postsUtils.recent(posts, 5);
+ $scope.alerts = [
+ { type: 'warning', msg: $sce.trustAsHtml('Warning: Best check yo self, you\'re not looking too good.') },
+ { type: 'success', msg: $sce.trustAsHtml('Success: You successfully read this important alert message.') },
+ { type: 'info', msg: $sce.trustAsHtml('Info: This alert needs your attention, but it\'s not super important.') },
+ { type: 'danger', msg: $sce.trustAsHtml('Danger: Change this and that and try again.'
+ + 'Ignore'
+ + 'Take this action') }
+ ];
+
+ $scope.addAlert = function() {
+ $scope.alerts.push({type: 'warning', msg: $sce.trustAsHtml('Another alert!')});
+ };
+
+ $scope.closeAlert = function(index) {
+ $scope.alerts.splice(index, 1);
+ };
+ }
+ };
+});
+
+const dashboardControllerModule = require('../../../../../src/app/modules/dashboard/dashboard.js');
+
+describe('dashboardController', () => {
+ let $scope, controller;
+
+ beforeEach(() => {
+ $scope = {};
+ controller = dashboardControllerModule.dashboardController;
+ });
+
+ it('should initialize controller with correct data', () => {
+ mockPostsUtils.postsDuringInterval.mockReturnValue([mockPosts[1]]);
+ mockPostsUtils.lastEdited.mockReturnValue(mockPosts[1]);
+ mockPostsUtils.recent.mockReturnValue([mockPosts[0]]);
+
+ controller($scope, mockSce, mockPosts, mockPostsUtils);
+
+ expect($scope.posts).toEqual(mockPosts);
+ expect($scope.postsLastMonth).toEqual([mockPosts[1]]);
+ expect($scope.lastEditedPost).toEqual(mockPosts[1]);
+ expect($scope.postsRecently).toEqual([mockPosts[0]]);
+ expect($scope.alerts.length).toBe(4);
+ });
+
+ it('should add a new alert when addAlert is called', () => {
+ controller($scope, mockSce, mockPosts, mockPostsUtils);
+ const initialAlertsCount = $scope.alerts.length;
+
+ $scope.addAlert();
+
+ expect($scope.alerts.length).toBe(initialAlertsCount + 1);
+ expect($scope.alerts[initialAlertsCount].type).toBe('warning');
+ expect($scope.alerts[initialAlertsCount].msg.__html).toContain('Another alert!');
+ });
+
+ it('should close an alert when closeAlert is called', () => {
+ controller($scope, mockSce, mockPosts, mockPostsUtils);
+ const initialAlertsCount = $scope.alerts.length;
+
+ $scope.closeAlert(1);
+
+ expect($scope.alerts.length).toBe(initialAlertsCount - 1);
+ expect($scope.alerts).not.toContainEqual(expect.objectContaining({ type: 'success' }));
+ });
+
+ it('should use $sce.trustAsHtml for alert messages', () => {
+ controller($scope, mockSce, mockPosts, mockPostsUtils);
+
+ expect(mockSce.trustAsHtml).toHaveBeenCalledTimes(4);
+ expect($scope.alerts[0].msg.__html).toContain('Warning:');
+ expect($scope.alerts[1].msg.__html).toContain('Success:');
+ expect($scope.alerts[2].msg.__html).toContain('Info:');
+ expect($scope.alerts[3].msg.__html).toContain('Danger:');
+ });
+
+ it('should call postsUtils methods with correct parameters', () => {
+ controller($scope, mockSce, mockPosts, mockPostsUtils);
+
+ expect(mockPostsUtils.postsDuringInterval).toHaveBeenCalledWith(mockPosts, 30);
+ expect(mockPostsUtils.lastEdited).toHaveBeenCalledWith(mockPosts);
+ expect(mockPostsUtils.recent).toHaveBeenCalledWith(mockPosts, 5);
+ });
+});
\ No newline at end of file
diff --git a/tests/src/app/modules/data/posts.sapient.test.js b/tests/src/app/modules/data/posts.sapient.test.js
new file mode 100644
index 0000000..5fd30e5
--- /dev/null
+++ b/tests/src/app/modules/data/posts.sapient.test.js
@@ -0,0 +1,99 @@
+// posts.test.js
+
+// Mock Angular
+global.angular = {
+ module: jest.fn().mockReturnThis(),
+ factory: jest.fn().mockReturnThis()
+};
+
+// Mock $resource
+const mockResource = jest.fn().mockReturnValue({
+ update: jest.fn()
+});
+
+// Import the file we want to test
+require('../../../../../src/app/modules/data/posts');
+
+// Extract the postsUtils factory function
+const postsUtilsFactory = global.angular.factory.mock.calls.find(call => call[0] === 'postsUtils')[1];
+const postsUtils = postsUtilsFactory(mockResource);
+
+describe('postsUtils', () => {
+ describe('postsDuringInterval', () => {
+ it('should return posts within the specified interval', () => {
+ const today = new Date();
+ const posts = [
+ { date: today.toISOString() },
+ { date: new Date(today.getTime() - 86400000).toISOString() },
+ { date: new Date(today.getTime() - 172800000).toISOString() },
+ ];
+
+ const result = postsUtils.postsDuringInterval(posts, 2);
+ expect(result.length).toBe(2);
+ expect(result).toContain(posts[0]);
+ expect(result).toContain(posts[1]);
+ });
+
+ it('should return an empty array if no posts are within the interval', () => {
+ const today = new Date();
+ const posts = [
+ { date: new Date(today.getTime() - 345600000).toISOString() }, // 4 days ago
+ ];
+
+ const result = postsUtils.postsDuringInterval(posts, 3);
+ expect(result).toEqual([]);
+ });
+ });
+
+ describe('recent', () => {
+ it('should return the most recent post when no count is specified', () => {
+ const posts = [
+ { date: '2023-05-01' },
+ { date: '2023-05-03' },
+ { date: '2023-05-02' },
+ ];
+
+ const result = postsUtils.recent(posts);
+ expect(result.length).toBe(1);
+ expect(result[0].date).toBe('2023-05-03');
+ });
+
+ it('should return the specified number of most recent posts', () => {
+ const posts = [
+ { date: '2023-05-01' },
+ { date: '2023-05-03' },
+ { date: '2023-05-02' },
+ { date: '2023-05-04' },
+ ];
+
+ const result = postsUtils.recent(posts, 2);
+ expect(result.length).toBe(2);
+ expect(result[0].date).toBe('2023-05-04');
+ expect(result[1].date).toBe('2023-05-03');
+ });
+ });
+
+ describe('lastEdited', () => {
+ it('should return the most recently edited post', () => {
+ const posts = [
+ { date: '2023-05-01' },
+ { date: '2023-05-03' },
+ { date: '2023-05-02' },
+ ];
+
+ const result = postsUtils.lastEdited(posts);
+ expect(result.date).toBe('2023-05-03');
+ });
+
+ it('should return the first post if all posts have the same date', () => {
+ const posts = [
+ { date: '2023-05-01', id: 1 },
+ { date: '2023-05-01', id: 2 },
+ { date: '2023-05-01', id: 3 },
+ ];
+
+ const result = postsUtils.lastEdited(posts);
+ expect(result).toEqual(posts[0]);
+ });
+ });
+});
\ No newline at end of file
diff --git a/tests/src/app/modules/profile/profile.module.sapient.test.js b/tests/src/app/modules/profile/profile.module.sapient.test.js
new file mode 100644
index 0000000..dee4726
--- /dev/null
+++ b/tests/src/app/modules/profile/profile.module.sapient.test.js
@@ -0,0 +1,67 @@
+// Set up global mock for angular
+global.angular = {
+ module: jest.fn(() => ({
+ config: jest.fn()
+ }))
+};
+
+// Import the module to be tested
+require('../../../../../src/app/modules/profile/profile.module.js');
+
+describe('app.profile module', () => {
+ it('should create an Angular module named app.profile', () => {
+ expect(global.angular.module).toHaveBeenCalledWith('app.profile', ['ui.router']);
+ });
+
+ it('should configure the module with appConfig', () => {
+ const mockModule = global.angular.module('app.profile', ['ui.router']);
+ expect(mockModule.config).toHaveBeenCalled();
+ });
+
+ describe('appConfig', () => {
+ let $stateProvider;
+ let configFn;
+
+ beforeEach(() => {
+ $stateProvider = {
+ state: jest.fn().mockReturnThis()
+ };
+ const mockModule = global.angular.module('app.profile', ['ui.router']);
+ configFn = mockModule.config.mock.calls[0][0];
+ });
+
+ it('should inject $stateProvider', () => {
+ expect(configFn.$inject).toEqual(['$stateProvider']);
+ });
+
+ it('should configure the login state', () => {
+ configFn($stateProvider);
+ expect($stateProvider.state).toHaveBeenCalledWith('login', {
+ url: '/login',
+ data: {
+ noAuth: true
+ },
+ templateUrl: 'app/modules/profile/auth/login.html',
+ controller: 'LoginController',
+ controllerAs: 'vm'
+ });
+ });
+
+ it('should configure the app.profile state', () => {
+ configFn($stateProvider);
+ expect($stateProvider.state).toHaveBeenCalledWith('app.profile', {
+ url: '/profile',
+ templateUrl: 'app/modules/profile/edit/edit.html',
+ controller: 'ProfileController',
+ controllerAs: 'vm'
+ });
+ });
+
+ it('should configure both states in the correct order', () => {
+ configFn($stateProvider);
+ expect($stateProvider.state.mock.calls.length).toBe(2);
+ expect($stateProvider.state.mock.calls[0][0]).toBe('login');
+ expect($stateProvider.state.mock.calls[1][0]).toBe('app.profile');
+ });
+ });
+});
\ No newline at end of file
diff --git a/tests/srv/util.sapient.test.js b/tests/srv/util.sapient.test.js
new file mode 100644
index 0000000..4186986
--- /dev/null
+++ b/tests/srv/util.sapient.test.js
@@ -0,0 +1,102 @@
+const { requestHelper, hidePassword } = require('../../srv/util');
+
+describe('requestHelper', () => {
+ it('should handle successful requests', async () => {
+ const mockReq = {};
+ const mockRes = {
+ send: jest.fn(),
+ };
+ const mockFunc = jest.fn().mockResolvedValue({ data: 'success' });
+
+ const wrappedFunc = requestHelper(mockFunc);
+ await wrappedFunc(mockReq, mockRes);
+
+ expect(mockFunc).toHaveBeenCalledWith(mockReq);
+ expect(mockRes.send).toHaveBeenCalledWith({ data: 'success' });
+ });
+
+ it('should handle errors with status code', async () => {
+ const mockReq = {};
+ const mockRes = {
+ status: jest.fn().mockReturnThis(),
+ send: jest.fn(),
+ };
+ const mockError = new Error('Test error');
+ mockError.statusCode = 400;
+ const mockFunc = jest.fn().mockRejectedValue(mockError);
+
+ const wrappedFunc = requestHelper(mockFunc);
+ await wrappedFunc(mockReq, mockRes);
+
+ expect(mockFunc).toHaveBeenCalledWith(mockReq);
+ expect(mockRes.status).toHaveBeenCalledWith(400);
+ expect(mockRes.send).toHaveBeenCalledWith({ message: 'Test error' });
+ });
+
+ it('should handle errors without status code', async () => {
+ const mockReq = {};
+ const mockRes = {
+ status: jest.fn().mockReturnThis(),
+ send: jest.fn(),
+ };
+ const mockError = new Error('Internal server error');
+ const mockFunc = jest.fn().mockRejectedValue(mockError);
+
+ const wrappedFunc = requestHelper(mockFunc);
+ await wrappedFunc(mockReq, mockRes);
+
+ expect(mockFunc).toHaveBeenCalledWith(mockReq);
+ expect(mockRes.status).toHaveBeenCalledWith(500);
+ expect(mockRes.send).toHaveBeenCalledWith({ message: 'Internal server error' });
+ });
+});
+
+describe('hidePassword', () => {
+ it('should remove password from user object', () => {
+ const user = {
+ id: 1,
+ username: 'testuser',
+ password: 'secret123',
+ email: 'test@example.com'
+ };
+
+ const result = hidePassword(user);
+
+ expect(result).toEqual({
+ id: 1,
+ username: 'testuser',
+ email: 'test@example.com'
+ });
+ expect(result).not.toHaveProperty('password');
+ });
+
+ it('should handle user object without password', () => {
+ const user = {
+ id: 1,
+ username: 'testuser',
+ email: 'test@example.com'
+ };
+
+ const result = hidePassword(user);
+
+ expect(result).toEqual(user);
+ });
+
+ it('should not modify the original user object', () => {
+ const user = {
+ id: 1,
+ username: 'testuser',
+ password: 'secret123',
+ email: 'test@example.com'
+ };
+
+ hidePassword(user);
+
+ expect(user).toEqual({
+ id: 1,
+ username: 'testuser',
+ password: 'secret123',
+ email: 'test@example.com'
+ });
+ });
+});
\ No newline at end of file