add tests

This commit is contained in:
Krrish Ghimire
2026-07-06 01:39:18 +05:45
parent f70fe81432
commit 51d887a005
15 changed files with 1850 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
package com.krrishg.config;
import org.junit.jupiter.api.Test;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.*;
class JwtAuthenticationTokenTest {
private final UserPrincipal principal = new UserPrincipal(
UUID.randomUUID(), "test@example.com", "Test User");
private final String token = "test-jwt-token";
private final JwtAuthenticationToken auth = new JwtAuthenticationToken(principal, token);
@Test
void constructorSetsAuthenticated() {
assertTrue(auth.isAuthenticated());
}
@Test
void getCredentialsReturnsToken() {
assertEquals(token, auth.getCredentials());
}
@Test
void getPrincipalReturnsUserPrincipal() {
assertSame(principal, auth.getPrincipal());
}
@Test
void getAuthoritiesContainsRoleUser() {
var authorities = auth.getAuthorities();
assertEquals(1, authorities.size());
assertEquals("ROLE_USER", authorities.iterator().next().getAuthority());
}
@Test
void principalFieldsAreAccessible() {
assertEquals("test@example.com", principal.email());
assertEquals("Test User", principal.name());
assertNotNull(principal.id());
}
@Test
void principalIsInstanceOfUserPrincipal() {
assertInstanceOf(UserPrincipal.class, auth.getPrincipal());
}
}

View File

@@ -0,0 +1,91 @@
package com.krrishg.controller;
import com.krrishg.config.GlobalExceptionHandler;
import com.krrishg.config.JwtAuthenticationToken;
import com.krrishg.config.UserPrincipal;
import com.krrishg.service.DeploymentService;
import com.krrishg.support.TestSecurityConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;
import java.util.UUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(DeploymentController.class)
@Import({GlobalExceptionHandler.class, TestSecurityConfig.class})
class DeploymentControllerTest {
private final MockMvc mockMvc;
@MockBean
private DeploymentService deploymentService;
private UUID userId;
private UUID siteId;
private UserPrincipal principal;
private JwtAuthenticationToken auth;
DeploymentControllerTest(@Autowired MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
@BeforeEach
void setUp() {
userId = UUID.randomUUID();
siteId = UUID.randomUUID();
principal = new UserPrincipal(userId, "user@test.com", "Test User");
auth = new JwtAuthenticationToken(principal, "test-token");
}
@Test
void publishReturns200() throws Exception {
when(deploymentService.publish(siteId)).thenReturn("https://mysite.example.com");
mockMvc.perform(post("/api/sites/{id}/publish", siteId)
.with(authentication(auth)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Site published successfully"))
.andExpect(jsonPath("$.url").value("https://mysite.example.com"));
}
@Test
void publishReturns400WhenNoVersion() throws Exception {
when(deploymentService.publish(siteId))
.thenThrow(new IllegalStateException("No generation versions found for site " + siteId));
mockMvc.perform(post("/api/sites/{id}/publish", siteId)
.with(authentication(auth)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.error").value("No generation versions found for site " + siteId));
}
@Test
void publishReturns400WhenNoRemote() throws Exception {
when(deploymentService.publish(siteId))
.thenThrow(new IllegalStateException("No git remote configured"));
mockMvc.perform(post("/api/sites/{id}/publish", siteId)
.with(authentication(auth)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.error").value("No git remote configured"));
}
@Test
void unpublishReturns200() throws Exception {
mockMvc.perform(post("/api/sites/{id}/unpublish", siteId)
.with(authentication(auth)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Site unpublished successfully"));
}
}

View File

@@ -0,0 +1,156 @@
package com.krrishg.controller;
import com.krrishg.config.GlobalExceptionHandler;
import com.krrishg.config.JwtAuthenticationToken;
import com.krrishg.config.UserPrincipal;
import com.krrishg.dto.SiteStructure;
import com.krrishg.model.GenerationVersion;
import com.krrishg.model.Site;
import com.krrishg.model.SiteStatus;
import com.krrishg.repository.SiteRepository;
import com.krrishg.service.ExportService;
import com.krrishg.service.LLMService;
import com.krrishg.support.TestSecurityConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(ExportController.class)
@Import({GlobalExceptionHandler.class, TestSecurityConfig.class})
class ExportControllerTest {
private final MockMvc mockMvc;
@MockBean
private ExportService exportService;
@MockBean
private LLMService llmService;
@MockBean
private SiteRepository siteRepository;
private UUID userId;
private UUID siteId;
private UserPrincipal principal;
private JwtAuthenticationToken auth;
private Site site;
ExportControllerTest(@Autowired MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
@BeforeEach
void setUp() {
userId = UUID.randomUUID();
siteId = UUID.randomUUID();
principal = new UserPrincipal(userId, "user@test.com", "Test User");
auth = new JwtAuthenticationToken(principal, "test-token");
site = Site.builder()
.id(siteId)
.userId(userId)
.name("My Site")
.subdomain("my-site")
.status(SiteStatus.DRAFT)
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.build();
}
@Test
void exportReturnsZipFile() throws Exception {
when(siteRepository.findById(siteId)).thenReturn(Optional.of(site));
GenerationVersion version = GenerationVersion.builder()
.id("v1")
.siteId(siteId)
.versionNumber(1)
.createdAt(LocalDateTime.now())
.build();
when(llmService.getVersions(siteId)).thenReturn(List.of(version));
when(llmService.restoreVersion("v1")).thenReturn(SiteStructure.builder().build());
when(exportService.exportSite(any(SiteStructure.class))).thenReturn("fake-zip-content".getBytes());
mockMvc.perform(get("/api/sites/{id}/export", siteId)
.with(authentication(auth)))
.andExpect(status().isOk())
.andExpect(header().string("Content-Disposition", "attachment; filename=\"my-site-export.zip\""))
.andExpect(header().string("Content-Type", "application/octet-stream"));
}
@Test
void exportReturns400WhenNoVersions() throws Exception {
when(siteRepository.findById(siteId)).thenReturn(Optional.of(site));
when(llmService.getVersions(siteId)).thenReturn(List.of());
mockMvc.perform(get("/api/sites/{id}/export", siteId)
.with(authentication(auth)))
.andExpect(status().isBadRequest());
}
@Test
void exportReturns500OnExportError() throws Exception {
when(siteRepository.findById(siteId)).thenReturn(Optional.of(site));
GenerationVersion version = GenerationVersion.builder()
.id("v1")
.siteId(siteId)
.versionNumber(1)
.createdAt(LocalDateTime.now())
.build();
when(llmService.getVersions(siteId)).thenReturn(List.of(version));
when(llmService.restoreVersion("v1")).thenReturn(SiteStructure.builder().build());
when(exportService.exportSite(any(SiteStructure.class)))
.thenThrow(new RuntimeException("Export failed"));
mockMvc.perform(get("/api/sites/{id}/export", siteId)
.with(authentication(auth)))
.andExpect(status().isInternalServerError());
}
@Test
void exportSanitizesFilenameForSiteWithoutSubdomain() throws Exception {
Site siteNoSubdomain = Site.builder()
.id(siteId)
.userId(userId)
.name("My Cool Site!")
.subdomain(null)
.status(SiteStatus.DRAFT)
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.build();
when(siteRepository.findById(siteId)).thenReturn(Optional.of(siteNoSubdomain));
GenerationVersion version = GenerationVersion.builder()
.id("v1")
.siteId(siteId)
.versionNumber(1)
.createdAt(LocalDateTime.now())
.build();
when(llmService.getVersions(siteId)).thenReturn(List.of(version));
when(llmService.restoreVersion("v1")).thenReturn(SiteStructure.builder().build());
when(exportService.exportSite(any(SiteStructure.class))).thenReturn("zip".getBytes());
mockMvc.perform(get("/api/sites/{id}/export", siteId)
.with(authentication(auth)))
.andExpect(status().isOk())
.andExpect(header().string("Content-Disposition",
"attachment; filename=\"my-cool-site-export.zip\""));
}
}

View File

@@ -0,0 +1,182 @@
package com.krrishg.controller;
import com.krrishg.config.GlobalExceptionHandler;
import com.krrishg.config.JwtAuthenticationToken;
import com.krrishg.config.UserPrincipal;
import com.krrishg.model.GitRemote;
import com.krrishg.repository.GitRemoteRepository;
import com.krrishg.service.GitService;
import com.krrishg.service.KeyManager;
import com.krrishg.support.TestSecurityConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(GitController.class)
@Import({GlobalExceptionHandler.class, TestSecurityConfig.class})
class GitControllerTest {
private final MockMvc mockMvc;
private final ObjectMapper objectMapper;
@MockBean
private GitService gitService;
@MockBean
private GitRemoteRepository gitRemoteRepository;
@MockBean
private KeyManager keyManager;
private UUID userId;
private UUID siteId;
private UserPrincipal principal;
private JwtAuthenticationToken auth;
GitControllerTest(@Autowired MockMvc mockMvc, @Autowired ObjectMapper objectMapper) {
this.mockMvc = mockMvc;
this.objectMapper = objectMapper;
}
@BeforeEach
void setUp() {
userId = UUID.randomUUID();
siteId = UUID.randomUUID();
principal = new UserPrincipal(userId, "user@test.com", "Test User");
auth = new JwtAuthenticationToken(principal, "test-token");
}
@Test
void connectRemoteReturns200() throws Exception {
when(gitRemoteRepository.findBySiteId(siteId)).thenReturn(Optional.empty());
when(keyManager.getPublicKey(siteId.toString())).thenReturn("ssh-ed25519 AAA...");
Map<String, String> request = Map.of(
"provider", "GITHUB",
"remoteUrl", "https://github.com/user/repo.git",
"token", "ghp_token"
);
mockMvc.perform(post("/api/sites/{id}/git/connect", siteId)
.with(authentication(auth))
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Remote connected successfully"))
.andExpect(jsonPath("$.provider").value("GITHUB"));
}
@Test
void connectRemoteReturns400WhenFieldsMissing() throws Exception {
Map<String, String> request = Map.of("provider", "GITHUB");
mockMvc.perform(post("/api/sites/{id}/git/connect", siteId)
.with(authentication(auth))
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest());
}
@Test
void connectRemoteReturns400WhenInvalidProvider() throws Exception {
Map<String, String> request = Map.of(
"provider", "INVALID",
"remoteUrl", "https://github.com/user/repo.git",
"token", "token"
);
mockMvc.perform(post("/api/sites/{id}/git/connect", siteId)
.with(authentication(auth))
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.error").value("Invalid provider. Must be GITHUB or GITLAB"));
}
@Test
void connectRemoteReplacesExistingRemote() throws Exception {
GitRemote existing = GitRemote.builder()
.siteId(siteId)
.provider(GitRemote.GitProvider.GITLAB)
.remoteUrl("https://gitlab.com/old/repo.git")
.encryptedToken("old-token")
.build();
when(gitRemoteRepository.findBySiteId(siteId)).thenReturn(Optional.of(existing));
when(keyManager.getPublicKey(siteId.toString())).thenReturn("ssh-ed25519 AAA...");
Map<String, String> request = Map.of(
"provider", "GITHUB",
"remoteUrl", "https://github.com/user/repo.git",
"token", "ghp_token"
);
mockMvc.perform(post("/api/sites/{id}/git/connect", siteId)
.with(authentication(auth))
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk());
verify(gitRemoteRepository).delete(existing);
}
@Test
void disconnectRemoteReturns200() throws Exception {
mockMvc.perform(post("/api/sites/{id}/git/disconnect", siteId)
.with(authentication(auth)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Remote disconnected"));
}
@Test
void getStatusReturnsConnectedInfo() throws Exception {
GitRemote remote = GitRemote.builder()
.siteId(siteId)
.provider(GitRemote.GitProvider.GITHUB)
.remoteUrl("https://github.com/user/repo.git")
.encryptedToken("token")
.build();
when(gitRemoteRepository.findBySiteId(siteId)).thenReturn(Optional.of(remote));
when(keyManager.hasKeyPair(siteId.toString())).thenReturn(true);
when(gitService.getLog(eq(siteId.toString()), eq(10))).thenReturn(List.of());
mockMvc.perform(get("/api/sites/{id}/git/status", siteId)
.with(authentication(auth)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.connected").value(true))
.andExpect(jsonPath("$.provider").value("GITHUB"))
.andExpect(jsonPath("$.remoteUrl").value("https://github.com/user/repo.git"))
.andExpect(jsonPath("$.hasKeys").value(true));
}
@Test
void getStatusReturnsDisconnectedState() throws Exception {
when(gitRemoteRepository.findBySiteId(siteId)).thenReturn(Optional.empty());
when(keyManager.hasKeyPair(siteId.toString())).thenReturn(false);
when(gitService.getLog(eq(siteId.toString()), eq(10))).thenReturn(List.of());
mockMvc.perform(get("/api/sites/{id}/git/status", siteId)
.with(authentication(auth)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.connected").value(false))
.andExpect(jsonPath("$.hasKeys").value(false));
}
}

View File

@@ -0,0 +1,106 @@
package com.krrishg.controller;
import com.krrishg.config.GlobalExceptionHandler;
import com.krrishg.config.JwtAuthenticationToken;
import com.krrishg.config.UserPrincipal;
import com.krrishg.service.MediaService;
import com.krrishg.support.TestSecurityConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import java.util.UUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(MediaController.class)
@Import({GlobalExceptionHandler.class, TestSecurityConfig.class})
class MediaControllerTest {
private final MockMvc mockMvc;
@MockBean
private MediaService mediaService;
private UUID userId;
private UUID siteId;
private UserPrincipal principal;
private JwtAuthenticationToken auth;
MediaControllerTest(@Autowired MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
@BeforeEach
void setUp() {
userId = UUID.randomUUID();
siteId = UUID.randomUUID();
principal = new UserPrincipal(userId, "user@test.com", "Test User");
auth = new JwtAuthenticationToken(principal, "test-token");
}
@Test
void uploadReturns200() throws Exception {
MediaService.MediaUploadResult result = new MediaService.MediaUploadResult(
"media/" + siteId + "/abc123.jpg",
"https://cdn.example.com/media/" + siteId + "/abc123.jpg",
"photo.jpg",
"image/jpeg",
1024L
);
when(mediaService.upload(any(), any())).thenReturn(result);
MockMultipartFile file = new MockMultipartFile(
"file", "photo.jpg", "image/jpeg", "test-image-content".getBytes());
mockMvc.perform(multipart("/api/media/upload")
.file(file)
.param("siteId", siteId.toString())
.with(authentication(auth)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.key").value("media/" + siteId + "/abc123.jpg"))
.andExpect(jsonPath("$.url").value("https://cdn.example.com/media/" + siteId + "/abc123.jpg"))
.andExpect(jsonPath("$.originalFilename").value("photo.jpg"))
.andExpect(jsonPath("$.contentType").value("image/jpeg"))
.andExpect(jsonPath("$.size").value(1024));
}
@Test
void uploadReturns400WhenInvalidFile() throws Exception {
when(mediaService.upload(any(), any()))
.thenThrow(new IllegalArgumentException("File type not allowed"));
MockMultipartFile file = new MockMultipartFile(
"file", "bad.exe", "application/x-msdownload", "bad".getBytes());
mockMvc.perform(multipart("/api/media/upload")
.file(file)
.param("siteId", siteId.toString())
.with(authentication(auth)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.error").value("File type not allowed"));
}
@Test
void getMediaUrlReturns200() throws Exception {
String key = "abc.jpg";
when(mediaService.getPublicUrl(key)).thenReturn("https://cdn.example.com/media/" + key);
mockMvc.perform(get("/api/media/{key}", key)
.with(authentication(auth)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.key").value(key))
.andExpect(jsonPath("$.url").value("https://cdn.example.com/media/abc.jpg"));
}
}

View File

@@ -0,0 +1,48 @@
package com.krrishg.engine;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class JsBundleTest {
private final JsBundle jsBundle = new JsBundle();
@Test
void compileReturnsNonEmptyJs() {
String js = jsBundle.compile();
assertNotNull(js);
assertFalse(js.isBlank());
}
@Test
void compileContainsDomContentLoaded() {
String js = jsBundle.compile();
assertTrue(js.contains("DOMContentLoaded"));
}
@Test
void compileContainsNavToggle() {
String js = jsBundle.compile();
assertTrue(js.contains("nav-toggle"));
}
@Test
void compileContainsSmoothScroll() {
String js = jsBundle.compile();
assertTrue(js.contains("scrollIntoView"));
}
@Test
void compileContainsLightboxHandler() {
String js = jsBundle.compile();
assertTrue(js.contains("lightbox"));
}
@Test
void compileContainsContactFormHandler() {
String js = jsBundle.compile();
assertTrue(js.contains("data-static"));
assertTrue(js.contains("Message sent"));
}
}

View File

@@ -0,0 +1,44 @@
package com.krrishg.engine;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class StyleCompilerTest {
private final StyleCompiler compiler = new StyleCompiler();
@Test
void compileReturnsNonEmptyCss() {
String css = compiler.compile();
assertNotNull(css);
assertFalse(css.isBlank());
}
@Test
void compileContainsCssVariables() {
String css = compiler.compile();
assertTrue(css.contains("--color-text"));
assertTrue(css.contains("--color-background"));
assertTrue(css.contains("--font-body"));
}
@Test
void compileContainsContainerMaxWidth() {
String css = compiler.compile();
assertTrue(css.contains("--spacing-containerWidth"));
}
@Test
void compileContainsResponsiveQueries() {
String css = compiler.compile();
assertTrue(css.contains("@media (max-width: 768px)"));
assertTrue(css.contains("@media (max-width: 480px)"));
}
@Test
void compileStartsWithUniversalReset() {
String css = compiler.compile();
assertTrue(css.startsWith("* { margin: 0; padding: 0; box-sizing: border-box; }"));
}
}

View File

@@ -0,0 +1,70 @@
package com.krrishg.model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class GitRemoteTest {
@Test
void onCreateSetsIdAndTimestamps() {
GitRemote remote = new GitRemote();
remote.onCreate();
assertNotNull(remote.getId());
assertNotNull(remote.getCreatedAt());
assertNotNull(remote.getUpdatedAt());
}
@Test
void onCreateDoesNotOverrideExistingId() {
java.util.UUID existingId = java.util.UUID.randomUUID();
GitRemote remote = GitRemote.builder()
.id(existingId)
.siteId(java.util.UUID.randomUUID())
.provider(GitRemote.GitProvider.GITHUB)
.remoteUrl("https://github.com/user/repo.git")
.encryptedToken("token")
.build();
remote.onCreate();
assertEquals(existingId, remote.getId());
}
@Test
void onUpdateSetsUpdatedAt() {
GitRemote remote = GitRemote.builder()
.siteId(java.util.UUID.randomUUID())
.provider(GitRemote.GitProvider.GITLAB)
.remoteUrl("https://gitlab.com/user/repo.git")
.encryptedToken("token")
.build();
remote.onCreate();
java.time.LocalDateTime before = remote.getUpdatedAt();
remote.onUpdate();
assertTrue(remote.getUpdatedAt().isAfter(before) || remote.getUpdatedAt().equals(before));
}
@Test
void builderSetsDefaults() {
GitRemote remote = GitRemote.builder()
.siteId(java.util.UUID.randomUUID())
.provider(GitRemote.GitProvider.GITHUB)
.remoteUrl("https://github.com/user/repo.git")
.encryptedToken("token")
.build();
assertEquals("main", remote.getDefaultBranch());
}
@Test
void gitProviderEnumValues() {
assertArrayEquals(
new GitRemote.GitProvider[]{GitRemote.GitProvider.GITHUB, GitRemote.GitProvider.GITLAB},
GitRemote.GitProvider.values()
);
}
}

View File

@@ -0,0 +1,91 @@
package com.krrishg.repository;
import com.krrishg.model.GitRemote;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import java.util.Optional;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.*;
@DataJpaTest
class GitRemoteRepositoryTest {
@Autowired
private GitRemoteRepository repository;
@Test
void findBySiteIdReturnsRemoteWhenExists() {
UUID siteId = UUID.randomUUID();
GitRemote remote = GitRemote.builder()
.siteId(siteId)
.provider(GitRemote.GitProvider.GITHUB)
.remoteUrl("https://github.com/user/repo.git")
.encryptedToken("encrypted-token")
.build();
repository.save(remote);
Optional<GitRemote> found = repository.findBySiteId(siteId);
assertTrue(found.isPresent());
assertEquals(siteId, found.get().getSiteId());
}
@Test
void findBySiteIdReturnsEmptyWhenNotExists() {
Optional<GitRemote> found = repository.findBySiteId(UUID.randomUUID());
assertTrue(found.isEmpty());
}
@Test
void existsBySiteIdReturnsTrueWhenExists() {
UUID siteId = UUID.randomUUID();
GitRemote remote = GitRemote.builder()
.siteId(siteId)
.provider(GitRemote.GitProvider.GITLAB)
.remoteUrl("https://gitlab.com/user/repo.git")
.encryptedToken("encrypted-token")
.build();
repository.save(remote);
assertTrue(repository.existsBySiteId(siteId));
}
@Test
void existsBySiteIdReturnsFalseWhenNotExists() {
assertFalse(repository.existsBySiteId(UUID.randomUUID()));
}
@Test
void deleteBySiteIdRemovesRemote() {
UUID siteId = UUID.randomUUID();
GitRemote remote = GitRemote.builder()
.siteId(siteId)
.provider(GitRemote.GitProvider.GITHUB)
.remoteUrl("https://github.com/user/repo.git")
.encryptedToken("encrypted-token")
.build();
repository.save(remote);
repository.deleteBySiteId(siteId);
assertFalse(repository.existsBySiteId(siteId));
}
@Test
void savedRemoteHasIdAndTimestamps() {
GitRemote remote = GitRemote.builder()
.siteId(UUID.randomUUID())
.provider(GitRemote.GitProvider.GITHUB)
.remoteUrl("https://github.com/user/repo.git")
.encryptedToken("encrypted-token")
.build();
GitRemote saved = repository.save(remote);
assertNotNull(saved.getId());
assertNotNull(saved.getCreatedAt());
assertNotNull(saved.getUpdatedAt());
}
}

View File

@@ -0,0 +1,47 @@
package com.krrishg.service;
import com.krrishg.model.AuthProvider;
import com.krrishg.model.User;
import org.junit.jupiter.api.Test;
import java.util.Map;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.*;
class CustomOAuth2UserTest {
private final User user = User.builder()
.id(UUID.randomUUID())
.email("user@example.com")
.name("Test User")
.provider(AuthProvider.GOOGLE)
.providerId("google-123")
.build();
private final Map<String, Object> attributes = Map.of("sub", "google-123", "email", "user@example.com");
private final CustomOAuth2User oAuth2User = new CustomOAuth2User(user, attributes);
@Test
void getNameReturnsUserId() {
assertEquals(user.getId().toString(), oAuth2User.getName());
}
@Test
void getAttributesReturnsProvidedMap() {
assertEquals(attributes, oAuth2User.getAttributes());
}
@Test
void getAuthoritiesContainsRoleUser() {
var authorities = oAuth2User.getAuthorities();
assertEquals(1, authorities.size());
assertEquals("ROLE_USER", authorities.iterator().next().getAuthority());
}
@Test
void getUserReturnsTheUser() {
assertSame(user, oAuth2User.getUser());
}
}

View File

@@ -0,0 +1,131 @@
package com.krrishg.service;
import com.krrishg.model.Site;
import com.krrishg.model.SiteStatus;
import com.krrishg.support.FakeSiteRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.*;
class DomainServiceTest {
private static final String SITE_DOMAIN = "indie.example.com";
private FakeSiteRepository siteRepository;
private DomainService domainService;
@BeforeEach
void setUp() {
siteRepository = new FakeSiteRepository();
domainService = new DomainService(siteRepository, SITE_DOMAIN);
}
@Test
void resolveSiteReturnsSiteWhenSubdomainMatches() {
Site site = Site.builder()
.userId(UUID.randomUUID())
.name("My Site")
.subdomain("mysite")
.status(SiteStatus.DRAFT)
.build();
siteRepository.save(site);
Optional<Site> result = domainService.resolveSite("mysite.indie.example.com");
assertTrue(result.isPresent());
assertEquals("mysite", result.get().getSubdomain());
}
@Test
void resolveSiteReturnsEmptyForUnknownSubdomain() {
Optional<Site> result = domainService.resolveSite("unknown.indie.example.com");
assertTrue(result.isEmpty());
}
@Test
void resolveSiteReturnsEmptyForNonMatchingHostname() {
Optional<Site> result = domainService.resolveSite("example.com");
assertTrue(result.isEmpty());
}
@Test
void resolveSiteReturnsEmptyWhenHostnameIsNull() {
Optional<Site> result = domainService.resolveSite(null);
assertTrue(result.isEmpty());
}
@Test
void resolveSiteReturnsEmptyWhenSubdomainContainsDots() {
Site site = Site.builder()
.userId(UUID.randomUUID())
.name("My Site")
.subdomain("mysite")
.status(SiteStatus.DRAFT)
.build();
siteRepository.save(site);
Optional<Site> result = domainService.resolveSite("mysite.staging.indie.example.com");
assertTrue(result.isEmpty());
}
@Test
void resolveSiteIsCaseInsensitiveOnHostname() {
Site site = Site.builder()
.userId(UUID.randomUUID())
.name("My Site")
.subdomain("mysite")
.status(SiteStatus.DRAFT)
.build();
siteRepository.save(site);
Optional<Site> result = domainService.resolveSite("MySite.indie.example.com");
assertTrue(result.isPresent());
assertEquals("mysite", result.get().getSubdomain());
}
@Test
void isSubdomainAvailableReturnsTrueWhenNotTaken() {
assertTrue(domainService.isSubdomainAvailable("available"));
}
@Test
void isSubdomainAvailableReturnsFalseWhenTaken() {
Site site = Site.builder()
.userId(UUID.randomUUID())
.name("My Site")
.subdomain("taken")
.status(SiteStatus.DRAFT)
.build();
siteRepository.save(site);
assertFalse(domainService.isSubdomainAvailable("taken"));
}
@Test
void getSiteUrlReturnsHttpsUrlWithSubdomain() {
Site site = Site.builder()
.userId(UUID.randomUUID())
.name("My Site")
.subdomain("mysite")
.status(SiteStatus.DRAFT)
.build();
String url = domainService.getSiteUrl(site);
assertEquals("https://mysite.indie.example.com", url);
}
@Test
void getSiteUrlReturnsNullWhenNoSubdomain() {
Site site = Site.builder()
.userId(UUID.randomUUID())
.name("My Site")
.status(SiteStatus.DRAFT)
.build();
assertNull(domainService.getSiteUrl(site));
}
}

View File

@@ -0,0 +1,83 @@
package com.krrishg.service;
import com.krrishg.dto.SiteStructure;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
class ExportServiceTest {
private final RenderService renderService = new RenderService(
new com.krrishg.engine.StyleCompiler(),
new com.krrishg.engine.JsBundle());
private final ExportService exportService = new ExportService(renderService);
@Test
void exportSiteReturnsNonEmptyZip() throws Exception {
SiteStructure structure = SiteStructure.builder()
.theme(SiteStructure.ThemeConfig.builder()
.colors(Map.of("primary", "#4f46e5"))
.build())
.pages(List.of(SiteStructure.PageStructure.builder()
.id("p1")
.title("Home")
.slug("home")
.rawHtml("<h1>Hello</h1>")
.build()))
.build();
byte[] result = exportService.exportSite(structure);
assertNotNull(result);
assertTrue(result.length > 0);
assertTrue(isValidZip(result));
}
@Test
void exportSiteZipContainsExpectedFiles() throws Exception {
SiteStructure structure = SiteStructure.builder()
.theme(SiteStructure.ThemeConfig.builder().build())
.pages(List.of(SiteStructure.PageStructure.builder()
.id("p1")
.title("Home")
.slug("home")
.rawHtml("<h1>Hello</h1>")
.build(),
SiteStructure.PageStructure.builder()
.id("p2")
.title("About")
.slug("about")
.rawHtml("<p>About</p>")
.build()))
.build();
byte[] result = exportService.exportSite(structure);
var entries = getZipEntries(result);
assertTrue(entries.contains("style.css"));
assertTrue(entries.contains("script.js"));
assertTrue(entries.contains("index.html"));
assertTrue(entries.contains("about/index.html"));
assertTrue(entries.contains("404.html"));
}
private boolean isValidZip(byte[] data) {
return data.length > 22
&& data[0] == 'P' && data[1] == 'K';
}
private java.util.Set<String> getZipEntries(byte[] data) throws Exception {
java.util.Set<String> names = new java.util.LinkedHashSet<>();
try (java.util.zip.ZipInputStream zis =
new java.util.zip.ZipInputStream(new java.io.ByteArrayInputStream(data))) {
java.util.zip.ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
names.add(entry.getName());
}
}
return names;
}
}

View File

@@ -0,0 +1,124 @@
package com.krrishg.service;
import com.krrishg.model.GitRemote;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*;
class PagesProviderTest {
private final GitRemote gitHubRemote = GitRemote.builder()
.siteId(java.util.UUID.randomUUID())
.provider(GitRemote.GitProvider.GITHUB)
.remoteUrl("https://github.com/username/my-site.git")
.encryptedToken("token")
.build();
private final GitRemote gitLabRemote = GitRemote.builder()
.siteId(java.util.UUID.randomUUID())
.provider(GitRemote.GitProvider.GITLAB)
.remoteUrl("https://gitlab.com/username/my-site.git")
.encryptedToken("token")
.build();
@Test
void forRemoteReturnsGitHubPagesForGitHubUrl() {
PagesProvider provider = PagesProvider.forRemote(gitHubRemote);
assertInstanceOf(GitHubPagesProvider.class, provider);
}
@Test
void forRemoteReturnsGitLabPagesForGitLabUrl() {
PagesProvider provider = PagesProvider.forRemote(gitLabRemote);
assertInstanceOf(GitLabPagesProvider.class, provider);
}
@Test
void gitHubBranchNameIsGhPages() {
PagesProvider provider = PagesProvider.forRemote(gitHubRemote);
assertEquals("gh-pages", provider.branchName());
}
@Test
void gitLabBranchNameIsGlPages() {
PagesProvider provider = PagesProvider.forRemote(gitLabRemote);
assertEquals("gl-pages", provider.branchName());
}
@Test
void gitHubContentRootIsTempDir() {
PagesProvider provider = PagesProvider.forRemote(gitHubRemote);
Path tempDir = Path.of("/tmp/test");
assertEquals(tempDir, provider.contentRoot(tempDir));
}
@Test
void gitLabContentRootIsPublicSubdir() {
PagesProvider provider = PagesProvider.forRemote(gitLabRemote);
Path tempDir = Path.of("/tmp/test");
assertEquals(tempDir.resolve("public"), provider.contentRoot(tempDir));
}
@Test
void gitHubWritesNoJekyllFile(@TempDir Path tempDir) throws Exception {
PagesProvider provider = PagesProvider.forRemote(gitHubRemote);
provider.writeProviderFiles(tempDir);
assertTrue(Files.exists(tempDir.resolve(".nojekyll")));
assertTrue(Files.readString(tempDir.resolve(".nojekyll")).isEmpty());
}
@Test
void gitLabWritesCiYmlFile(@TempDir Path tempDir) throws Exception {
PagesProvider provider = PagesProvider.forRemote(gitLabRemote);
provider.writeProviderFiles(tempDir);
Path ciYml = tempDir.resolve(".gitlab-ci.yml");
assertTrue(Files.exists(ciYml));
String content = Files.readString(ciYml);
assertTrue(content.contains("gl-pages"));
assertTrue(content.contains("public"));
}
@Test
void gitHubBuildPagesUrl() {
PagesProvider provider = PagesProvider.forRemote(gitHubRemote);
String url = provider.buildPagesUrl(gitHubRemote);
assertEquals("https://username.github.io/my-site/", url);
}
@Test
void gitLabBuildPagesUrl() {
PagesProvider provider = PagesProvider.forRemote(gitLabRemote);
String url = provider.buildPagesUrl(gitLabRemote);
assertEquals("https://username.gitlab.io/my-site/", url);
}
@Test
void gitHubBuildPagesUrlHandlesTrailingSlash() {
GitRemote remote = GitRemote.builder()
.siteId(java.util.UUID.randomUUID())
.provider(GitRemote.GitProvider.GITHUB)
.remoteUrl("https://github.com/username/my-site/")
.encryptedToken("token")
.build();
PagesProvider provider = PagesProvider.forRemote(remote);
assertEquals("https://username.github.io/my-site/", provider.buildPagesUrl(remote));
}
@Test
void gitLabBuildPagesUrlHandlesSshUrl() {
GitRemote remote = GitRemote.builder()
.siteId(java.util.UUID.randomUUID())
.provider(GitRemote.GitProvider.GITLAB)
.remoteUrl("git@gitlab.com:username/my-site.git")
.encryptedToken("token")
.build();
PagesProvider provider = PagesProvider.forRemote(remote);
assertEquals("https://username.gitlab.io/my-site/", provider.buildPagesUrl(remote));
}
}

View File

@@ -0,0 +1,203 @@
package com.krrishg.service;
import com.krrishg.dto.SiteStructure;
import com.krrishg.engine.JsBundle;
import com.krrishg.engine.StyleCompiler;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
class RenderServiceTest {
private final StyleCompiler styleCompiler = new StyleCompiler();
private final JsBundle jsBundle = new JsBundle();
private final RenderService renderService = new RenderService(styleCompiler, jsBundle);
@Test
void renderSiteReturnsOutputWithCssAndJs() {
SiteStructure structure = SiteStructure.builder()
.theme(SiteStructure.ThemeConfig.builder()
.colors(Map.of("primary", "#4f46e5", "background", "#ffffff"))
.fonts(Map.of("body", "Inter"))
.spacing(Map.of("containerWidth", "1100px"))
.build())
.pages(List.of(SiteStructure.PageStructure.builder()
.id("p1")
.title("Home")
.slug("home")
.rawHtml("<h1>Hello</h1>")
.build()))
.build();
RenderService.SiteOutput output = renderService.renderSite(structure);
assertNotNull(output.css());
assertNotNull(output.js());
assertEquals(1, output.pages().size());
}
@Test
void renderSiteHandlesNullPages() {
SiteStructure structure = SiteStructure.builder()
.theme(SiteStructure.ThemeConfig.builder().build())
.build();
RenderService.SiteOutput output = renderService.renderSite(structure);
assertNotNull(output.pages());
assertTrue(output.pages().isEmpty());
}
@Test
void renderSiteHandlesNullTheme() {
SiteStructure structure = SiteStructure.builder()
.pages(List.of(SiteStructure.PageStructure.builder()
.id("p1")
.title("Home")
.slug("home")
.rawHtml("<h1>Hello</h1>")
.build()))
.build();
RenderService.SiteOutput output = renderService.renderSite(structure);
assertNotNull(output.css());
assertEquals(1, output.pages().size());
}
@Test
void renderPageUsesSeoTitleWhenPresent() {
SiteStructure structure = SiteStructure.builder()
.theme(SiteStructure.ThemeConfig.builder().build())
.pages(List.of(SiteStructure.PageStructure.builder()
.id("p1")
.title("Home")
.slug("home")
.seoTitle("Custom SEO Title")
.rawHtml("<p>Content</p>")
.build()))
.build();
RenderService.SiteOutput output = renderService.renderSite(structure);
String html = output.pages().get("home");
assertTrue(html.contains("<title>Custom SEO Title</title>"));
}
@Test
void renderPageFallsBackToTitleWhenNoSeoTitle() {
SiteStructure structure = SiteStructure.builder()
.theme(SiteStructure.ThemeConfig.builder().build())
.pages(List.of(SiteStructure.PageStructure.builder()
.id("p1")
.title("Fallback Title")
.slug("home")
.rawHtml("<p>Content</p>")
.build()))
.build();
RenderService.SiteOutput output = renderService.renderSite(structure);
String html = output.pages().get("home");
assertTrue(html.contains("<title>Fallback Title</title>"));
}
@Test
void renderPageIncludesCssVars() {
SiteStructure structure = SiteStructure.builder()
.theme(SiteStructure.ThemeConfig.builder()
.colors(Map.of("primary", "#4f46e5"))
.fonts(Map.of("body", "Inter"))
.spacing(Map.of("containerWidth", "1100px"))
.build())
.pages(List.of(SiteStructure.PageStructure.builder()
.id("p1")
.title("Home")
.slug("home")
.rawHtml("<p>Content</p>")
.build()))
.build();
RenderService.SiteOutput output = renderService.renderSite(structure);
String html = output.pages().get("home");
assertTrue(html.contains("--color-primary: #4f46e5"));
assertTrue(html.contains("--font-body: Inter"));
assertTrue(html.contains("--spacing-containerWidth: 1100px"));
}
@Test
void siteOutputAsFileMapIncludesStyleAndScript() {
SiteStructure structure = SiteStructure.builder()
.theme(SiteStructure.ThemeConfig.builder().build())
.pages(List.of(SiteStructure.PageStructure.builder()
.id("p1")
.title("Home")
.slug("home")
.rawHtml("<p>Content</p>")
.build()))
.build();
RenderService.SiteOutput output = renderService.renderSite(structure);
Map<String, byte[]> files = output.asFileMap();
assertTrue(files.containsKey("style.css"));
assertTrue(files.containsKey("script.js"));
assertTrue(files.containsKey("404.html"));
}
@Test
void siteOutputAsFileMapPutsHomePageAsIndexHtml() {
SiteStructure structure = SiteStructure.builder()
.theme(SiteStructure.ThemeConfig.builder().build())
.pages(List.of(SiteStructure.PageStructure.builder()
.id("p1")
.title("Home")
.slug("home")
.rawHtml("<p>Content</p>")
.build()))
.build();
RenderService.SiteOutput output = renderService.renderSite(structure);
Map<String, byte[]> files = output.asFileMap();
assertTrue(files.containsKey("index.html"));
}
@Test
void siteOutputAsFileMapPutsNonHomePageInSubdir() {
SiteStructure structure = SiteStructure.builder()
.theme(SiteStructure.ThemeConfig.builder().build())
.pages(List.of(SiteStructure.PageStructure.builder()
.id("p1")
.title("About")
.slug("about")
.rawHtml("<p>About us</p>")
.build()))
.build();
RenderService.SiteOutput output = renderService.renderSite(structure);
Map<String, byte[]> files = output.asFileMap();
assertTrue(files.containsKey("about/index.html"));
}
@Test
void siteOutputAsFileMapContains404Html() {
SiteStructure structure = SiteStructure.builder()
.theme(SiteStructure.ThemeConfig.builder().build())
.pages(List.of())
.build();
RenderService.SiteOutput output = renderService.renderSite(structure);
Map<String, byte[]> files = output.asFileMap();
assertTrue(files.containsKey("404.html"));
String content = new String(files.get("404.html"));
assertTrue(content.contains("404"));
assertTrue(content.contains("Go Home"));
}
}