• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FIWARE / contract-management / #103

30 Sep 2026 02:39PM UTC coverage: 4.528% (+0.7%) from 3.796%
#103

Pull #28

vramperez
Address review: keep the standard stacktrace, only add the downstream answer

- DownstreamError no longer rewrites the cause chain into a custom format; it
  only adds what the stacktrace lacks: status and body of the downstream
  answer a failure was caused by
- The single place logging a failure attaches the exception, so the standard
  stacktrace is printed once per failure (subscription retries and faulty
  client requests stay one line)
- Removed remaining duplicate logs: the ContractManagementController and
  Oid4VpAuthHandler logged errors that are propagated and logged again; the
  OID4VP token failure is wrapped with client and scope instead

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pull Request #28: Add failure reasons, downstream error descriptions and configurable log output

213 of 403 new or added lines in 35 files covered. (52.85%)

11 existing lines in 7 files now uncovered.

1621 of 35799 relevant lines covered (4.53%)

0.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.82
/src/main/java/org/fiware/iam/tmforum/OrganizationResolver.java
1
package org.fiware.iam.tmforum;
2

3
import com.fasterxml.jackson.databind.ObjectMapper;
4
import io.micronaut.context.annotation.Requires;
5
import io.micronaut.context.annotation.Value;
6
import io.micronaut.http.HttpStatus;
7
import jakarta.inject.Singleton;
8
import lombok.RequiredArgsConstructor;
9
import lombok.extern.slf4j.Slf4j;
10
import org.fiware.iam.configuration.GeneralProperties;
11
import org.fiware.iam.domain.ContractManagement;
12
import io.micronaut.http.client.exceptions.HttpClientResponseException;
13
import org.fiware.iam.exception.FailureReason;
14
import org.fiware.iam.exception.TMForumException;
15
import org.fiware.iam.tmforum.party.api.OrganizationApiClient;
16
import org.fiware.iam.tmforum.party.model.CharacteristicVO;
17
import org.fiware.iam.tmforum.party.model.ExternalReferenceVO;
18
import org.fiware.iam.tmforum.party.model.OrganizationVO;
19
import reactor.core.publisher.Mono;
20

21
import java.util.List;
22
import java.util.Optional;
23

24
@Requires(condition = GeneralProperties.TmForumCondition.class)
25
@Singleton
26
@Slf4j
1 ✔
27
@RequiredArgsConstructor
28
public class OrganizationResolver {
29

30
    @Value("${general.organization.provider.role:provider}")
31
    private String PROVIDER_ROLE;
32

33
    private static final String PARTY_CHARACTERISTIC_DID = "did";
34
    private static final String FIELD_NAME_CONTRACT_MANAGEMENT = "contractManagement";
35
    private static final String EXTERNAL_REFERENCE_IDM_ID = "idm_id";
36
    private static final String DID = "did";
37

38
    private final GeneralProperties generalProperties;
39
    private final ObjectMapper objectMapper;
40
    private final OrganizationApiClient apiClient;
41

42
    //TODO Cache me if you can
43
    public Mono<String> getDID(String organizationId) {
44
        return retrieveOrganization(organizationId)
1 ✔
45
                .map(ovo -> getDid(organizationId, ovo));
1 ✔
46
    }
47

48
    public Mono<ContractManagement> getContractManagement(String organizationId) {
NEW
49
        return retrieveOrganization(organizationId)
×
NEW
50
                .map(ovo -> {
×
NEW
51
                    if (getDid(organizationId, ovo).equals(generalProperties.getDid())) {
×
NEW
52
                        return new ContractManagement(true);
×
53
                    }
NEW
54
                    return Optional.ofNullable(ovo.getPartyCharacteristic())
×
NEW
55
                            .orElse(List.of())
×
NEW
56
                            .stream()
×
NEW
57
                            .filter(pc -> FIELD_NAME_CONTRACT_MANAGEMENT.equals(pc.getName()))
×
NEW
58
                            .map(CharacteristicVO::getValue)
×
NEW
59
                            .map(pcv -> objectMapper.convertValue(pcv, ContractManagement.class))
×
NEW
60
                            .findAny()
×
NEW
61
                            .orElse(new ContractManagement(true));
×
62
                });
63
    }
64

65
    private Mono<OrganizationVO> retrieveOrganization(String organizationId) {
66
        return apiClient.retrieveOrganization(organizationId, null)
1 ✔
67
                .onErrorMap(HttpClientResponseException.class, e -> new TMForumException(FailureReason.ORGANIZATION_NOT_FOUND,
1 ✔
68
                        "Organization %s could not be retrieved from the TM Forum party API.".formatted(organizationId), e))
1 ✔
69
                .map(response -> {
1 ✔
70
                    if (!response.getStatus().equals(HttpStatus.OK) || response.body() == null) {
1 ✔
71
                        throw new TMForumException(FailureReason.ORGANIZATION_NOT_FOUND,
1 ✔
72
                                "Organization %s could not be retrieved from the TM Forum party API, it answered with status %s and %s.".formatted(
1 ✔
73
                                        organizationId, response.getStatus().getCode(), response.body() == null ? "no body" : "a body"));
1 ✔
74
                    }
75
                    return response.body();
1 ✔
76
                })
77
                .switchIfEmpty(Mono.error(() -> new TMForumException(FailureReason.ORGANIZATION_NOT_FOUND,
1 ✔
NEW
78
                        "Organization %s could not be retrieved from the TM Forum party API, the response was empty.".formatted(organizationId))));
×
79
    }
80

81
    private String getDid(String organizationId, OrganizationVO ovo) {
82
        String did = getDidFromExternalReference(ovo.getExternalReference())
1 ✔
83
                .or(() -> getDidFromPartyCharacteristics(ovo.getPartyCharacteristic()))
1 ✔
84
                .orElseThrow(() -> new TMForumException(FailureReason.ORGANIZATION_DID_MISSING,
1 ✔
85
                        "Organization %s has no valid DID in an externalReference of type '%s' or a partyCharacteristic '%s'.".formatted(
1 ✔
86
                                organizationId, EXTERNAL_REFERENCE_IDM_ID, PARTY_CHARACTERISTIC_DID)));
87
        log.debug("Organization {} has DID {}", organizationId, did);
1 ✔
88
        return did;
1 ✔
89
    }
90

91
    public boolean hasProviderRole(String role) {
92
        return PROVIDER_ROLE.equalsIgnoreCase(role);
×
93
    }
94

95
    private Optional<String> getDidFromPartyCharacteristics(List<CharacteristicVO> characteristicVOS) {
96
        if (characteristicVOS == null) {
1 ✔
97
            return Optional.empty();
1 ✔
98
        }
99
        return characteristicVOS.stream()
×
100
                .filter(entry -> PARTY_CHARACTERISTIC_DID.equals(entry.getName()))
×
101
                .map(CharacteristicVO::getValue)
×
102
                .filter(e -> e instanceof String)
×
103
                .map(e -> (String) e)
×
104
                .filter(this::isDid)
×
105
                .findAny();
×
106
    }
107

108
    private Optional<String> getDidFromExternalReference(List<ExternalReferenceVO> externalReferenceVOList) {
109
        if (externalReferenceVOList == null) {
1 ✔
110
            return Optional.empty();
1 ✔
111
        }
112
        return externalReferenceVOList.stream()
1 ✔
113
                .filter(ervo -> EXTERNAL_REFERENCE_IDM_ID.equals(ervo.getExternalReferenceType()))
1 ✔
114
                .map(ExternalReferenceVO::getName)
1 ✔
115
                .filter(this::isDid)
1 ✔
116
                .findFirst();
1 ✔
117
    }
118

119
    private boolean isDid(String id) {
120
        String[] idParts = id.split(":");
1 ✔
121
        return idParts.length >= 3 && idParts[0].equals(DID);
1 ✔
122
    }
123
}
124

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc