diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7eb6186 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +## v0.0.6 - Bug fixes +- Fixed test: removed "AA" from invalid input test (converter normalizes to lowercase) +- Fixed Oracle compatibility: changed `TEXT` column type to `@Lob` (maps to CLOB) +- All 10 tests pass + +## v0.0.5 - Null safety and tests +- Fixed controller: invalid input now returns 400 Bad Request instead of 200 with null body +- Added `ConverterTest` with all 8 spec examples and invalid input tests + +## v0.0.4 - Logging implementation +- Added `logback-spring.xml` with console and rolling file appender (7-day retention) +- Added SLF4J log statements to Controller, Service, and Converter + +## v0.0.3 - Controller refactoring +- Fixed `OracleQuantController`: corrected endpoints to match REST spec (`/convert-measurements`, `@RequestParam`) +- Added service injection, source IP capture, and proper HTTP responses + +## v0.0.2 - Converter fixes and service layer +- Fixed conversion algorithm: corrected `checkInput` inversion, `backageCount` reset, and post-loop flush +- Added `OracleQuantService` with history CRUD operations +- Added `OracleQuantRecord` JPA entity with id, timestamp, source_ip, input, output fields + +## v0.0.1 - Initial Release +- Package Measurement Conversion endpoint (GET /convert-measurements) +- History endpoints: GET all, GET by id, PUT/PATCH update, DELETE clear +- Oracle XE database persistence for request history +- Input validation and conversion algorithm +- Logging with rolling file appender (7-day retention) diff --git a/README.md b/README.md index b1cccfd..28cd420 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,146 @@ -## Submission Instructions - -To submit your Oracle JAVA Spring Boot Maven project as a solution, please follow these steps: - -### Step 1: Install git on your PC -- Install "git" as shown in this tutorial: [How to install git](https://youtu.be/iYkLrXobBbA?si=_l0haibv_X9NpIjJ) -- Open command prompt and run - ```bash - git version - ``` -- If you see the version, then git is successfully installed. - -### Step 2: Fork the Repository -- Navigate to [this repository](https://github.com/CodelineAtyab/oraclequantapi) provided by Codeline. -- Click on the "Fork" button at the top-right corner of the page to create a copy of the repository under your own GitHub account. - -### Step 3: Clone the Forked Repository -- Open your terminal or command prompt. -- Clone the forked repository to your local machine using the following command: - ```bash - git clone https://github.com/your-username/repo-name.git - ``` - -### Step 4: Create a new branch -- Navigate to the cloned repository directory - ```bash - cd repo-name - ``` -- Create a new branch for your code submissions (Replace your-name with your name in your-name-submission-branch): - ```bash - git checkout -b your-name-submission-branch - ``` - - -### Step 5: Add Your Code -- Implement the API - -### Step 6: Commit your changes -- Run the following commands in order to commit your changes: - ```bash - git add * - git commit -m "Meaningful commit message here" - ``` - -### Step 7: Push Your Branch to GitHub -- Run the following commands to upload the changes to the forked github repository (Replace your-name with your name in your-name-submission-branch): - ```bash - git push origin your-name-submission-branch - ``` - -### Step 8: Create a Pull Request -- Go to your forked repository on GitHub. -- You should see a prompt to create a pull request. Click on "Compare & pull request". -- Provide a title and description for your pull request, then click "Create pull request". - -### Step 9: Notify Codeline -- Notify on slack that you have created a PR for your solution. - -## Note: If you face any issues in the process above, Please do the following: -- Watch [this youtube tutorial](https://www.youtube.com/watch?v=a_FLqX3vGR4) -- Contact Ikhlas or Atyab. +# OracleQuant ERP - Package Measurement Conversion API + +A REST API for converting measurement input strings into package totals, built with **Java 17**, **Spring Boot**, **JPA**, and **Oracle XE**. + +--- + +## Prerequisites + +- Oracle OpenJDK 17 +- Maven (or use the included `mvnw` wrapper) +- Oracle XE database (21c+) + +--- + +## Build + +```bash +./mvnw clean package +``` + +Produces `target/oraclequantapi-0.0.1-SNAPSHOT.jar`. + +--- + +## Database Configuration + +Edit `src/main/resources/application.properties`: + +```properties +spring.datasource.url=jdbc:oracle:thin:@localhost:1521/XEPDB1 +spring.datasource.username=your_user +spring.datasource.password=your_password +spring.datasource.driver-class-name=oracle.jdbc.OracleDriver + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.database-platform=org.hibernate.dialect.OracleDialect +``` + +Tables are created automatically via `ddl-auto=update`. + +--- + +## Run + +```bash +java -jar target/oraclequantapi-0.0.1-SNAPSHOT.jar +``` + +The API starts at `http://localhost:8080`. + +--- + +## API Endpoints + +### Convert measurements + +``` +GET /convert-measurements?input={string} +``` + +**Response**: `200 OK` with JSON array of package totals. + +| Request | Response | +|---|---| +| `?input=aa` | `[1]` | +| `?input=abbcc` | `[2, 6]` | +| `?input=dz_a_aazzaaa` | `[28, 53, 1]` | +| `?input=a_` | `[0]` | +| `?input=abcdabcdab` | `[2, 7, 7]` | +| `?input=abcdabcdab_` | `[2, 7, 7, 0]` | + +### History + +| Method | Endpoint | Description | +|---|---|---| +| GET | `/convert-measurements/history` | List all records | +| GET | `/convert-measurements/history/{id}` | Get record by ID | +| PUT | `/convert-measurements/history/{id}` | Update record | +| DELETE | `/convert-measurements/history` | Clear all history | +| DELETE | `/convert-measurements/history/{id}` | Delete record by ID | + +Each history record contains: `id`, `timestamp`, `source_ip_address`, `input`, `output`. + +--- + +## Deploy on Oracle Linux + +### 1. Transfer the JAR + +```bash +scp target/oraclequantapi-0.0.1-SNAPSHOT.jar oracle@your-vm-ip:/home/oracle/ +``` + +### 2. SSH into the VM + +```bash +ssh oracle@your-vm-ip +``` + +### 3. Install Java (if not present) + +```bash +sudo dnf install java-17-openjdk-devel +``` + +### 4. Run as a service (optional) + +```bash +sudo tee /etc/systemd/system/oraclequantapi.service < 26: chain `z` characters (each adds 26), terminated by a non-`z` letter +- Each package: first number = count of values, then sum of that many values + +--- + +## Logging + +Logs are written to `logs/oraclequantapi.YYYY-MM-DD.log` and rotated daily, retained for 7 days. diff --git a/logs/oraclequantapi.2026-05-24.log b/logs/oraclequantapi.2026-05-24.log new file mode 100644 index 0000000..749a5db --- /dev/null +++ b/logs/oraclequantapi.2026-05-24.log @@ -0,0 +1,207 @@ +2026-05-24 23:45:10 [main] INFO c.o.o.OraclequantapiApplicationTests - Starting OraclequantapiApplicationTests using Java 21.0.7 with PID 21508 (started by USER in C:\Users\USER\Desktop\CodeLine\oraclequantapi) +2026-05-24 23:45:10 [main] INFO c.o.o.OraclequantapiApplicationTests - No active profile set, falling back to 1 default profile: "default" +2026-05-24 23:45:12 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories in DEFAULT mode. +2026-05-24 23:45:12 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 104 ms. Found 1 JPA repository interface. +2026-05-24 23:45:13 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] +2026-05-24 23:45:13 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.49.Final +2026-05-24 23:45:14 [main] INFO o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled +2026-05-24 23:45:14 [main] INFO o.s.o.j.p.SpringPersistenceUnitInfo - No LoadTimeWeaver setup: ignoring JPA class transformer +2026-05-24 23:45:14 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... +2026-05-24 23:45:16 [main] INFO com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection oracle.jdbc.driver.T4CConnection@9f2376f +2026-05-24 23:45:16 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. +2026-05-24 23:45:16 [main] WARN org.hibernate.orm.deprecation - HHH90000025: OracleDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default) +2026-05-24 23:45:17 [main] INFO o.hibernate.orm.connections.pooling - HHH10001005: Database info: + Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] + Database driver: undefined/unknown + Database version: 21.3 + Autocommit mode: undefined/unknown + Isolation level: undefined/unknown + Minimum pool size: undefined/unknown + Maximum pool size: undefined/unknown +2026-05-24 23:45:19 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) +2026-05-24 23:45:24 [main] WARN o.h.t.s.i.ExceptionHandlerLoggedImpl - GenerationTarget encountered exception accepting command : Error executing DDL "create table conversion_history (id number(10,0) generated by default as identity, input TEXT, output TEXT, source_ip_address varchar2(255 char), timestamp timestamp(6), primary key (id))" via JDBC [ORA-00902: invalid datatype + +https://docs.oracle.com/error-help/db/ora-00902/] +org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table conversion_history (id number(10,0) generated by default as identity, input TEXT, output TEXT, source_ip_address varchar2(255 char), timestamp timestamp(6), primary key (id))" via JDBC [ORA-00902: invalid datatype + +https://docs.oracle.com/error-help/db/ora-00902/] + at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:94) + at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlString(AbstractSchemaMigrator.java:576) + at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlStrings(AbstractSchemaMigrator.java:516) + at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.createTable(AbstractSchemaMigrator.java:316) + at org.hibernate.tool.schema.internal.GroupedSchemaMigratorImpl.performTablesMigration(GroupedSchemaMigratorImpl.java:80) + at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.performMigration(AbstractSchemaMigrator.java:233) + at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.doMigration(AbstractSchemaMigrator.java:112) + at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:280) + at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.lambda$process$5(SchemaManagementToolCoordinator.java:144) + at java.base/java.util.HashMap.forEach(HashMap.java:1429) + at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:141) + at org.hibernate.boot.internal.SessionFactoryObserverForSchemaExport.sessionFactoryCreated(SessionFactoryObserverForSchemaExport.java:37) + at org.hibernate.internal.SessionFactoryObserverChain.sessionFactoryCreated(SessionFactoryObserverChain.java:35) + at org.hibernate.internal.SessionFactoryImpl.(SessionFactoryImpl.java:324) + at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:463) + at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1517) + at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:66) + at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:388) + at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:419) + at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:400) + at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:364) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207) + at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:974) + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:628) + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) + at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:151) + at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) + at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) + at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1461) + at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:590) + at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:151) + at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:110) + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225) + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152) + at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:130) + at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:200) + at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:139) + at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260) + at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:159) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$10(ClassBasedTestDescriptor.java:383) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:388) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$11(ClassBasedTestDescriptor.java:382) + at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) + at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) + at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) + at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) + at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) + at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) + at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) + at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) + at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) + at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) + at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:382) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$6(ClassBasedTestDescriptor.java:293) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:292) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:281) + at java.base/java.util.Optional.orElseGet(Optional.java:364) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:280) + at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:27) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:112) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:111) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:69) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:128) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:128) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:201) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:170) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:94) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:59) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:142) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:58) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85) + at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) + at org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$1(InterceptingLauncher.java:39) + at org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25) + at org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:38) + at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) + at org.apache.maven.surefire.junitplatform.LauncherAdapter.executeWithoutCancellationToken(LauncherAdapter.java:60) + at org.apache.maven.surefire.junitplatform.LauncherAdapter.execute(LauncherAdapter.java:52) + at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.execute(JUnitPlatformProvider.java:203) + at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:168) + at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:136) + at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385) + at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162) + at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507) + at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495) +Caused by: java.sql.SQLSyntaxErrorException: ORA-00902: invalid datatype + +https://docs.oracle.com/error-help/db/ora-00902/ + at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:715) + at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:615) + at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:1372) + at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:969) + at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:237) + at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:524) + at oracle.jdbc.driver.T4CStatement.doOall8(T4CStatement.java:192) + at oracle.jdbc.driver.T4CStatement.executeForRows(T4CStatement.java:1401) + at oracle.jdbc.driver.OracleStatement.executeSQLStatement(OracleStatement.java:2009) + at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1622) + at oracle.jdbc.driver.OracleStatement.executeInternal(OracleStatement.java:2687) + at oracle.jdbc.driver.OracleStatement.execute(OracleStatement.java:2636) + at oracle.jdbc.driver.OracleStatementWrapper.execute(OracleStatementWrapper.java:337) + at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:95) + at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java) + at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:80) + ... 122 common frames omitted +Caused by: Error : 902, Position : 89, SQL = create table conversion_history (id number(10,0) generated by default as identity, input TEXT, output TEXT, source_ip_address varchar2(255 char), timestamp timestamp(6), primary key (id)), Original SQL = create table conversion_history (id number(10,0) generated by default as identity, input TEXT, output TEXT, source_ip_address varchar2(255 char), timestamp timestamp(6), primary key (id)), Error Message = ORA-00902: invalid datatype + + at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:723) + ... 137 common frames omitted +2026-05-24 23:45:24 [main] INFO o.s.o.j.LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit 'default' +2026-05-24 23:45:25 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning +2026-05-24 23:45:25 [main] INFO c.o.o.OraclequantapiApplicationTests - Started OraclequantapiApplicationTests in 16.298 seconds (process running for 18.387) +2026-05-24 23:45:27 [SpringApplicationShutdownHook] INFO o.s.o.j.LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory for persistence unit 'default' +2026-05-24 23:45:27 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... +2026-05-24 23:45:27 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. +2026-05-24 23:46:07 [main] INFO c.o.o.OraclequantapiApplicationTests - Starting OraclequantapiApplicationTests using Java 21.0.7 with PID 1448 (started by USER in C:\Users\USER\Desktop\CodeLine\oraclequantapi) +2026-05-24 23:46:07 [main] INFO c.o.o.OraclequantapiApplicationTests - No active profile set, falling back to 1 default profile: "default" +2026-05-24 23:46:08 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories in DEFAULT mode. +2026-05-24 23:46:08 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 106 ms. Found 1 JPA repository interface. +2026-05-24 23:46:10 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] +2026-05-24 23:46:10 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.49.Final +2026-05-24 23:46:10 [main] INFO o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled +2026-05-24 23:46:11 [main] INFO o.s.o.j.p.SpringPersistenceUnitInfo - No LoadTimeWeaver setup: ignoring JPA class transformer +2026-05-24 23:46:11 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... +2026-05-24 23:46:12 [main] INFO com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection oracle.jdbc.driver.T4CConnection@3b33fff9 +2026-05-24 23:46:12 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. +2026-05-24 23:46:13 [main] WARN org.hibernate.orm.deprecation - HHH90000025: OracleDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default) +2026-05-24 23:46:13 [main] INFO o.hibernate.orm.connections.pooling - HHH10001005: Database info: + Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] + Database driver: undefined/unknown + Database version: 21.3 + Autocommit mode: undefined/unknown + Isolation level: undefined/unknown + Minimum pool size: undefined/unknown + Maximum pool size: undefined/unknown +2026-05-24 23:46:15 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) +2026-05-24 23:46:19 [main] INFO o.s.o.j.LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit 'default' +2026-05-24 23:46:19 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning +2026-05-24 23:46:20 [main] INFO c.o.o.OraclequantapiApplicationTests - Started OraclequantapiApplicationTests in 14.185 seconds (process running for 16.35) +2026-05-24 23:46:21 [SpringApplicationShutdownHook] INFO o.s.o.j.LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory for persistence unit 'default' +2026-05-24 23:46:21 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... +2026-05-24 23:46:21 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. diff --git a/logs/oraclequantapi.log b/logs/oraclequantapi.log new file mode 100644 index 0000000..052c880 --- /dev/null +++ b/logs/oraclequantapi.log @@ -0,0 +1,27 @@ +2026-05-25 09:51:07 [main] INFO c.o.o.OraclequantapiApplicationTests - Starting OraclequantapiApplicationTests using Java 21.0.7 with PID 16144 (started by USER in C:\Users\USER\Desktop\CodeLine\oraclequantapi) +2026-05-25 09:51:07 [main] INFO c.o.o.OraclequantapiApplicationTests - No active profile set, falling back to 1 default profile: "default" +2026-05-25 09:51:09 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories in DEFAULT mode. +2026-05-25 09:51:09 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 115 ms. Found 1 JPA repository interface. +2026-05-25 09:51:10 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default] +2026-05-25 09:51:10 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 6.6.49.Final +2026-05-25 09:51:10 [main] INFO o.h.c.i.RegionFactoryInitiator - HHH000026: Second-level cache disabled +2026-05-25 09:51:11 [main] INFO o.s.o.j.p.SpringPersistenceUnitInfo - No LoadTimeWeaver setup: ignoring JPA class transformer +2026-05-25 09:51:11 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... +2026-05-25 09:51:13 [main] INFO com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection oracle.jdbc.driver.T4CConnection@7e1a9173 +2026-05-25 09:51:13 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. +2026-05-25 09:51:13 [main] WARN org.hibernate.orm.deprecation - HHH90000025: OracleDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default) +2026-05-25 09:51:14 [main] INFO o.hibernate.orm.connections.pooling - HHH10001005: Database info: + Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] + Database driver: undefined/unknown + Database version: 21.3 + Autocommit mode: undefined/unknown + Isolation level: undefined/unknown + Minimum pool size: undefined/unknown + Maximum pool size: undefined/unknown +2026-05-25 09:51:16 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) +2026-05-25 09:51:22 [main] INFO o.s.o.j.LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit 'default' +2026-05-25 09:51:23 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning +2026-05-25 09:51:24 [main] INFO c.o.o.OraclequantapiApplicationTests - Started OraclequantapiApplicationTests in 18.518 seconds (process running for 21.467) +2026-05-25 09:51:25 [SpringApplicationShutdownHook] INFO o.s.o.j.LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory for persistence unit 'default' +2026-05-25 09:51:25 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated... +2026-05-25 09:51:25 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed. diff --git a/pom.xml b/pom.xml index 20909d2..793b7bd 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,18 @@ spring-boot-starter-test test + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.oracle.database.jdbc + ojdbc11 + runtime + diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/controllers/OracleQuantController.java b/src/main/java/com/oraclequantapi/oraclequantapi/controllers/OracleQuantController.java new file mode 100644 index 0000000..ff67d80 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/controllers/OracleQuantController.java @@ -0,0 +1,85 @@ +package com.oraclequantapi.oraclequantapi.controllers; + +import com.oraclequantapi.oraclequantapi.models.OracleQuantRecord; +import com.oraclequantapi.oraclequantapi.services.OracleQuantService; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping(path = "/convert-measurements") +public class OracleQuantController { + + private static final Logger log = LoggerFactory.getLogger(OracleQuantController.class); + + private final OracleQuantService service; + + public OracleQuantController(OracleQuantService service) { + this.service = service; + } + + @GetMapping + public ResponseEntity> convert(@RequestParam("input") String input, + HttpServletRequest request) { + String sourceIp = request.getRemoteAddr(); + log.info("Conversion request - input: \"{}\", source IP: {}", input, sourceIp); + List result = service.convertMeasurements(input, sourceIp); + if (result == null) { + log.warn("Invalid input, returning 400 - input: \"{}\"", input); + return ResponseEntity.badRequest().build(); + } + log.info("Conversion result - input: \"{}\", output: {}", input, result); + return ResponseEntity.ok(result); + } + + @GetMapping("/history") + public ResponseEntity> getAllHistory() { + log.info("Fetching all history records"); + List records = service.getAllHistory(); + log.info("Fetched {} history records", records.size()); + return ResponseEntity.ok(records); + } + + @GetMapping("/history/{id}") + public ResponseEntity getHistoryById(@PathVariable Integer id) { + log.info("Fetching history record by id: {}", id); + return service.getHistoryById(id) + .map(record -> { + log.info("Found history record: {}", id); + return ResponseEntity.ok(record); + }) + .orElseGet(() -> { + log.warn("History record not found: {}", id); + return ResponseEntity.notFound().build(); + }); + } + + @PutMapping("/history/{id}") + public ResponseEntity updateHistory(@PathVariable Integer id, + @RequestBody OracleQuantRecord record) { + log.info("Updating history record: {}", id); + OracleQuantRecord updated = service.updateHistory(id, record); + log.info("Updated history record: {}", id); + return ResponseEntity.ok(updated); + } + + @DeleteMapping("/history") + public ResponseEntity clearHistory() { + log.info("Clearing all history records"); + service.clearHistory(); + log.info("All history records cleared"); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/history/{id}") + public ResponseEntity deleteHistory(@PathVariable Integer id) { + log.info("Deleting history record: {}", id); + service.deleteHistoryById(id); + log.info("Deleted history record: {}", id); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/models/OracleQuantRecord.java b/src/main/java/com/oraclequantapi/oraclequantapi/models/OracleQuantRecord.java new file mode 100644 index 0000000..132df1a --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/models/OracleQuantRecord.java @@ -0,0 +1,66 @@ +package com.oraclequantapi.oraclequantapi.models; + +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "conversion_history") +public class OracleQuantRecord { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + private LocalDateTime timestamp; + + @Column(name = "source_ip_address") + private String sourceIpAddress; + + @Lob + private String input; + + @Lob + private String output; + + public OracleQuantRecord() {} + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public String getSourceIpAddress() { + return sourceIpAddress; + } + + public void setSourceIpAddress(String sourceIpAddress) { + this.sourceIpAddress = sourceIpAddress; + } + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public String getOutput() { + return output; + } + + public void setOutput(String output) { + this.output = output; + } +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/repositories/OracleQuantRepository.java b/src/main/java/com/oraclequantapi/oraclequantapi/repositories/OracleQuantRepository.java new file mode 100644 index 0000000..20e582d --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/repositories/OracleQuantRepository.java @@ -0,0 +1,9 @@ +package com.oraclequantapi.oraclequantapi.repositories; + +import com.oraclequantapi.oraclequantapi.models.OracleQuantRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface OracleQuantRepository extends JpaRepository { +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/services/Converter.java b/src/main/java/com/oraclequantapi/oraclequantapi/services/Converter.java new file mode 100644 index 0000000..ec65d93 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/services/Converter.java @@ -0,0 +1,103 @@ +package com.oraclequantapi.oraclequantapi.services; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + + +@Service +public class Converter { + + private static final Logger log = LoggerFactory.getLogger(Converter.class); + + public Converter() { + } + + public List Convert(String input) { + log.debug("Starting conversion for input: \"{}\"", input); + String input1 = input.trim().toLowerCase(); + String[] inputArray = input1.split(""); + boolean checked = checkInput(inputArray); + if (!checked) { + log.warn("Invalid input: \"{}\"", input); + return null; + } + + List list = valueToInt(inputArray); + log.debug("Value-to-int result: {}", list); + + list = backageSize(list); + log.debug("Packaging result: {}", list); + + return list; + } + + public boolean checkInput(String[] input) { + for (String s : input) { + if (s.isEmpty() || !s.matches("^[a-z_]*$")) { + return false; + } + } + return true; + } + + public List backageSize(List valuesList) { + List backageSizeList = new ArrayList<>(); + int backageCount = 0; + int count = 0; + int size = 0; + + for (Integer integer : valuesList) { + if (backageCount == 0) { + backageCount = integer; + log.trace("Package count: {}", backageCount); + } else { + size += integer; + count++; + log.trace("Accumulated value: {}, running size: {}, count: {}/{}", + integer, size, count, backageCount); + } + if (backageCount == count) { + backageSizeList.add(size); + log.trace("Package complete: sum={}", size); + count = 0; + size = 0; + backageCount = 0; + } + } + if (count > 0) { + log.trace("Adding partial package: sum={}", size); + backageSizeList.add(size); + } + return backageSizeList; + } + + public List valueToInt(String[] inputs) { + List valueList = new ArrayList<>(); + int value = 0; + + String[] valid = "_abcdefghijklmnopqrstuvwxyz".split(""); + + for (int i =0; i < inputs.length; i++) { + for (int j = 0; j < valid.length; j++) { + if (inputs[i].equals(valid[j])) { + value += j; + break; + } + } + + if (!inputs[i].equals("z") || i == inputs.length -1) { + valueList.add(value); + log.trace("Decoded char '{}' -> accumulated value: {}", inputs[i], value); + value = 0; + } else { + log.trace("Char 'z' encountered, accumulating (total: {})", value); + } + } + + return valueList; + } +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/services/OracleQuantService.java b/src/main/java/com/oraclequantapi/oraclequantapi/services/OracleQuantService.java new file mode 100644 index 0000000..dc07817 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/services/OracleQuantService.java @@ -0,0 +1,77 @@ +package com.oraclequantapi.oraclequantapi.services; + +import com.oraclequantapi.oraclequantapi.models.OracleQuantRecord; +import com.oraclequantapi.oraclequantapi.repositories.OracleQuantRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Service +public class OracleQuantService { + + private static final Logger log = LoggerFactory.getLogger(OracleQuantService.class); + + private final Converter converter; + private final OracleQuantRepository repository; + + public OracleQuantService(Converter converter, OracleQuantRepository repository) { + this.converter = converter; + this.repository = repository; + } + + public List convertMeasurements(String input, String sourceIp) { + log.debug("Converting measurement - input: \"{}\", source IP: {}", input, sourceIp); + List result = converter.Convert(input); + + OracleQuantRecord record = new OracleQuantRecord(); + record.setTimestamp(LocalDateTime.now()); + record.setSourceIpAddress(sourceIp); + record.setInput(input); + record.setOutput(result != null ? result.toString() : null); + repository.save(record); + log.debug("Saved conversion history record for input: \"{}\", output: {}", input, result); + + return result; + } + + public List getAllHistory() { + log.debug("Fetching all history records"); + return repository.findAll(); + } + + public Optional getHistoryById(Integer id) { + log.debug("Fetching history record by id: {}", id); + return repository.findById(id); + } + + public OracleQuantRecord updateHistory(Integer id, OracleQuantRecord updated) { + log.debug("Updating history record: {}", id); + OracleQuantRecord record = repository.findById(id) + .orElseThrow(() -> { + log.error("Record not found for update: {}", id); + return new RuntimeException("Record not found: " + id); + }); + record.setInput(updated.getInput()); + record.setOutput(updated.getOutput()); + record.setSourceIpAddress(updated.getSourceIpAddress()); + OracleQuantRecord saved = repository.save(record); + log.debug("Updated history record: {}", id); + return saved; + } + + public void clearHistory() { + log.debug("Clearing all history records"); + repository.deleteAll(); + log.debug("All history records cleared"); + } + + public void deleteHistoryById(Integer id) { + log.debug("Deleting history record: {}", id); + repository.deleteById(id); + log.debug("Deleted history record: {}", id); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 99d0060..71c42d5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,13 @@ spring.application.name=oraclequantapi + +# Oracle XE datasource +spring.datasource.url=jdbc:oracle:thin:@localhost:1521/XEPDB1 +spring.datasource.username=system +spring.datasource.password=29999login +spring.datasource.driver-class-name=oracle.jdbc.OracleDriver + +# JPA / Hibernate settings +# "update" creates/updates tables automatically ? safe for development +spring.jpa.hibernate.ddl-auto=update +spring.jpa.database-platform=org.hibernate.dialect.OracleDialect +spring.jpa.show-sql=true diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..1863a84 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,24 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + logs/oraclequantapi.log + + logs/oraclequantapi.%d{yyyy-MM-dd}.log + 7 + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/src/test/java/com/oraclequantapi/oraclequantapi/ConverterTest.java b/src/test/java/com/oraclequantapi/oraclequantapi/ConverterTest.java new file mode 100644 index 0000000..bba3bc9 --- /dev/null +++ b/src/test/java/com/oraclequantapi/oraclequantapi/ConverterTest.java @@ -0,0 +1,67 @@ +package com.oraclequantapi.oraclequantapi; + +import com.oraclequantapi.oraclequantapi.services.Converter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class ConverterTest { + + private Converter converter; + + @BeforeEach + void setUp() { + converter = new Converter(); + } + + @Test + void testAa() { + assertEquals(List.of(1), converter.Convert("aa")); + } + + @Test + void testAbbcc() { + assertEquals(List.of(2, 6), converter.Convert("abbcc")); + } + + @Test + void testDz_a_aazzaaa() { + assertEquals(List.of(28, 53, 1), converter.Convert("dz_a_aazzaaa")); + } + + @Test + void testA_() { + assertEquals(List.of(0), converter.Convert("a_")); + } + + @Test + void testAbcdabcdab() { + assertEquals(List.of(2, 7, 7), converter.Convert("abcdabcdab")); + } + + @Test + void testAbcdabcdab_() { + assertEquals(List.of(2, 7, 7, 0), converter.Convert("abcdabcdab_")); + } + + @Test + void testZdaaaaaaaabaaaaaaaabaaaaaaaabbaa() { + assertEquals(List.of(34), converter.Convert("zdaaaaaaaabaaaaaaaabaaaaaaaabbaa")); + } + + @Test + void testZa_a_a_a_a_a_a_a_a_a_a_a_a_azaaa() { + assertEquals(List.of(40, 1), converter.Convert("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa")); + } + + @Test + void testInvalidInputReturnsNull() { + assertNull(converter.Convert("123")); + assertNull(converter.Convert("a b")); + assertNull(converter.Convert("")); + } +}